Import Tint changes from Dawn

Manual fixes:
- Add DXC to DEPS
- Add `dawn_dxc_dir` to `build_overrides/dawn.gni`
- Add TINT_WERROR definition and use it to enable -Werror

Changes:
  - 1ea04e752a1f85df223e0ba0d2b049a130167fcb [tint] Avoid using function source in diagnostics by James Price <jrprice@google.com>
  - 8a594c537587331be4a3c48d9d53717a5cccf461 [tint][ir][fuzz] Fix ClampFragDepth fuzzer by Ben Clayton <bclayton@google.com>
  - 5976efe6b4b72c822085cf7ee08ad00036888ab5 [tint] Link libraries for dlopen by James Price <jrprice@google.com>
  - 8752d497a948919675cbd4e7c539b0123211c28c [tint] Fix TINT_BEGIN_DISABLE_ALL_WARNINGS for GCC by James Price <jrprice@google.com>
  - 1888e152b77a3870d4c930d0fc2bb0828f9e1ffb [tint][spirv] Remove use of locale by Ben Clayton <bclayton@google.com>
  - de601a8efdbbdeb56c97c8d44bc34bba63be4b2c [ir] Add Function::AppendParam() helper by James Price <jrprice@google.com>
  - d9ca1a8b1a64c83775ceb4f9274f209b7c86a491 [uniformity] Fix ICE for pointer parameters by James Price <jrprice@google.com>
  - 6be95cec0b75d0d08cba9b56f56d4ed4ee5ee64e Refactor Inspector::GetTextureQueries by Shrek Shao <shrekshao@google.com>
  - c45c5d233a48501220d4e7252e836590128c72cd [msl] Refactor barrier polyfills by James Price <jrprice@google.com>
  - 1450d1d7f79b7f684bdac5196c3e06a86655034f CMake: Compile without -Werror by default. by Corentin Wallez <cwallez@chromium.org>
  - d3bebe6cf996439ed197d1c83e323baf8a9a7135 Support Coord Transformation for TextureLoadExternal() by Yan,Shaobo <shaobo.yan@intel.com>
  - 4975e45e94803c81563f0c290585a526d4c089d1 [spirv-reader] Handle OpIAdd instructions by James Price <jrprice@google.com>
  - 15445d1cbae1fef071af596f4b94faa1a5f7c709 [spirv-reader] Accept DepthReplacing by James Price <jrprice@google.com>
  - 92c7dbe391f09d42c2c081b96f00a5cb7bffe34d [tint][exe] Use `lower` when reading SPIR-V to IR by James Price <jrprice@google.com>
  - 2ee454636849c6debbc85f147a4cdf9d023ce43d [ir] Fix TINT_DUMP_IR_WHEN_VALIDATING by James Price <jrprice@google.com>
  - c410a83088d21c03d94e8058ac727c91364b7220 [tint][ast] Skip ClampFragDepth fuzzer if member offsets ... by Ben Clayton <bclayton@google.com>
  - 10d27c4c5ec062424acebd221d57a9fa3a670336 [spirv-reader] Handle OpFMul instructions by James Price <jrprice@google.com>
  - 9531d78bcf74288ae6d3ebd1132a15282a793857 [spirv-reader] Handle OpFAdd instructions by James Price <jrprice@google.com>
  - 415bd731243a5714a60064648b66f8e4491f47fe [tint][diagnostics] Remove System enum by Ben Clayton <bclayton@google.com>
  - 9dc707121317b1c7481f6ae0425c716ab1dc8bbb [tint][diagnostics] Remove ICE / Fatal severities by Ben Clayton <bclayton@google.com>
  - 5bd57050e50f0480d7753e9589cd4f28328185fa tint: make FindExecutable look in explicit CWD-prefixed p... by Antonio Maiorano <amaiorano@google.com>
  - 5350eb99d6d11d3f4d2bf1942d176277eb043621 hlsl ast fuzzer: validate hlsl with dxc by Antonio Maiorano <amaiorano@google.com>
  - 2f84301ccb3e4e2eb67c44b253510466094c4fc9 [tint][ir] Set the File on Disassembly's Sources by Ben Clayton <bclayton@google.com>
  - 3aebf9ec38e16b4717aa52382c7f7d47a510efba [tint][ir] Validate value scoping by Ben Clayton <bclayton@google.com>
  - c28fff58849a1447fe87e558550561706258e4f4 [tint][fuzz] Support passing fuzz::wgsl::Options to the W... by Ben Clayton <bclayton@google.com>
  - 833b892ea02779af23e83a244eaa445b5f4c6eb7 [tint][ir] Validate blocks without using recursion by Ben Clayton <bclayton@google.com>
  - 9d2a27af4a05926c5fee2e245883c61b1e6d5bec [tint][ir] Don't use capitalized diagnostic messages by Ben Clayton <bclayton@google.com>
  - 35db5b565e3a043b33b05c211447b016edc12acd [tint][ir] Have the Validator use the Disassembly::NameOf() by Ben Clayton <bclayton@google.com>
  - aaeb83a4fbcd6d5cf04cf62a837270326c3cf7f7 [tint][ir] Refactor the Disassembler, rename to Disassembly by Ben Clayton <bclayton@google.com>
  - 4ac690d07be2dfa1fee676558514c11a9d392571 tint_cmd: use DXC via shared library (dxcompiler) rather ... by Antonio Maiorano <amaiorano@google.com>
  - 9ae003a28e62d882e6bda2ea1a998dfea61b6bc9 [ir] Remove unused worklist by James Price <jrprice@google.com>
  - 425cb428fa9598dcdb549256f6a626236f64344c [tint][ir] Use styled text in validation errors by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: 1ea04e752a1f85df223e0ba0d2b049a130167fcb
Change-Id: I6c6a440e3ecdc72a40f0d8726146eee07ee76e2e
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/186661
Commit-Queue: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: Antonio Maiorano <amaiorano@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9df25d9..1930b90 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -108,6 +108,8 @@
 option_if_not_defined(TINT_ENABLE_ASAN "Enable address sanitizer" OFF)
 option_if_not_defined(TINT_ENABLE_UBSAN "Enable undefined behaviour sanitizer" OFF)
 
+option_if_not_defined(TINT_WERROR "Build with -Werror (or equivalent)" OFF)
+
 option_if_not_defined(TINT_ENABLE_BREAK_IN_DEBUGGER "Enable tint::debugger::Break()" OFF)
 
 option_if_not_defined(TINT_EMIT_COVERAGE "Emit code coverage information" OFF)
@@ -160,6 +162,10 @@
       -Wno-unknown-warning-option
     )
 
+    if (${TINT_WERROR})
+        target_compile_options(${TARGET} PRIVATE -Werror)
+    endif()
+
     if (${TINT_ENABLE_MSAN})
       target_compile_options(${TARGET} PUBLIC -fsanitize=memory)
       target_link_options(${TARGET} PUBLIC -fsanitize=memory)
diff --git a/DEPS b/DEPS
index 55f27cc..d04dd55 100644
--- a/DEPS
+++ b/DEPS
@@ -111,6 +111,10 @@
     'url': '{chromium_git}/chromium/src/third_party/abseil-cpp@4ef9b33175828ea46d091e7e5ec28259d39a8ba5',
   },
 
+  'third_party/dxc': {
+    'url': '{chromium_git}/external/github.com/microsoft/DirectXShaderCompiler@e7b78ff9c99c19a6a0c98256db9794e0af4eb59d',
+  },
+
   # Dependencies required for testing
   'testing': {
     'url': '{chromium_git}/chromium/src/testing@035a9b18047370df7403758b006e6c9696d6b84d',
diff --git a/build_overrides/dawn.gni b/build_overrides/dawn.gni
index 6440625..b34d77f 100644
--- a/build_overrides/dawn.gni
+++ b/build_overrides/dawn.gni
@@ -37,6 +37,7 @@
 dawn_vulkan_loader_dir = "//third_party/vulkan-deps/vulkan-loader/src"
 dawn_vulkan_validation_layers_dir =
     "//third_party/vulkan-deps/vulkan-validation-layers/src"
+dawn_dxc_dir = "//third_party/dxc"
 
 # Optional path to a one-liner version file. Default is empty path indicating
 # that git should be used to figure out the version.
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index f01ff5e..30d21d4 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -139,6 +139,10 @@
   }
 }
 
+source_set("dl") {
+  # GN doesn't appear to need to depend on any dl libraries.
+}
+
 source_set("thread") {
   # GN doesn't appear to need to depend on any thread libraries.
 }
@@ -252,6 +256,13 @@
   libs = [ "ws2_32.lib" ]
 }
 
+config("dxc-include-config") {
+  include_dirs = [ "${dawn_dxc_dir}/include" ]
+}
+source_set("dxc-include") {
+  public_configs = [ ":dxc-include-config" ]
+}
+
 ###############################################################################
 # Fuzzers
 ###############################################################################
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 235349c..80cbdfa 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -82,13 +82,15 @@
 
   set(COMMON_GNU_OPTIONS
     -Wall
-    -Werror
     -Wextra
     -Wno-documentation-unknown-command
     -Wno-padded
     -Wno-switch-enum
     -Wno-unknown-pragmas
   )
+  if (${TINT_WERROR})
+      list(APPEND COMMON_GNU_OPTIONS -Werror)
+  endif()
 
   set(COMMON_CLANG_OPTIONS
     -Wno-c++98-compat
@@ -130,7 +132,6 @@
       /bigobj
       /EHsc
       /W4
-      /WX
       /wd4068 # unknown pragma
       /wd4127 # conditional expression is constant
       /wd4244 # 'conversion' conversion from 'type1' to 'type2', possible loss of data
@@ -148,6 +149,9 @@
       /wd5026 # 'type': move constructor was implicitly defined as deleted
       /wd5027 # 'type': move assignment operator was implicitly defined as deleted
     )
+    if (${TINT_WERROR})
+      target_compile_options(${TARGET} PRIVATE /WX)
+    endif()
 
     # When building with clang-cl on Windows, try to match our clang build
     # options as much as possible.
@@ -501,6 +505,10 @@
       target_link_libraries(${TARGET} PRIVATE
         absl_strings
       )
+    elseif(${DEPENDENCY} STREQUAL "dl")
+      target_link_libraries(${TARGET} PRIVATE ${CMAKE_DL_LIBS})
+    elseif(${DEPENDENCY} STREQUAL "dxc-include")
+      target_include_directories(${TARGET} PRIVATE "${TINT_THIRD_PARTY_DIR}/dxc/include")
     elseif(${DEPENDENCY} STREQUAL "jsoncpp")
       target_link_libraries(${TARGET} PRIVATE jsoncpp_static)
     elseif(${DEPENDENCY} STREQUAL "langsvr")
diff --git a/src/tint/cmd/common/helper.cc b/src/tint/cmd/common/helper.cc
index a461591..49fcf79 100644
--- a/src/tint/cmd/common/helper.cc
+++ b/src/tint/cmd/common/helper.cc
@@ -124,17 +124,23 @@
             exit(1);
         }
 
-        // Convert the IR module to a WGSL AST program.
-        tint::wgsl::writer::ProgramOptions options;
-        options.allow_non_uniform_derivatives =
+        // Convert the IR module to a WGSL string.
+        tint::wgsl::writer::ProgramOptions writer_options;
+        writer_options.allow_non_uniform_derivatives =
             opts.spirv_reader_options.allow_non_uniform_derivatives;
-        options.allowed_features = opts.spirv_reader_options.allowed_features;
-        auto ast = tint::wgsl::writer::IRToProgram(result.Get(), options);
-        if (!ast.IsValid() || ast.Diagnostics().ContainsErrors()) {
-            std::cerr << "Failed to convert IR to AST:\n\n" << ast.Diagnostics() << "\n";
+        writer_options.allowed_features = opts.spirv_reader_options.allowed_features;
+        auto wgsl_result = tint::wgsl::writer::WgslFromIR(result.Get(), writer_options);
+        if (wgsl_result != Success) {
+            std::cerr << "Failed to convert IR to WGSL:\n\n"
+                      << wgsl_result.Failure().reason << "\n";
             exit(1);
         }
-        return ast;
+
+        // Parse the WGSL string to produce a WGSL AST.
+        tint::wgsl::reader::Options reader_options;
+        reader_options.allowed_features = tint::wgsl::AllowedFeatures::Everything();
+        auto file = std::make_unique<tint::Source::File>(opts.filename, wgsl_result->wgsl);
+        return tint::wgsl::reader::Parse(file.get(), reader_options);
 #else
         std::cerr << "Tint not built with the WGSL writer enabled" << std::endl;
         exit(1);
diff --git a/src/tint/cmd/fuzz/ir/fuzz.cc b/src/tint/cmd/fuzz/ir/fuzz.cc
index 3ea4363..a74fef3 100644
--- a/src/tint/cmd/fuzz/ir/fuzz.cc
+++ b/src/tint/cmd/fuzz/ir/fuzz.cc
@@ -64,7 +64,8 @@
 void Register(const IRFuzzer& fuzzer) {
     wgsl::Register({
         fuzzer.name,
-        [fn = fuzzer.fn](const Program& program, Slice<const std::byte> data) {
+        [fn = fuzzer.fn](const Program& program, const fuzz::wgsl::Options& options,
+                         Slice<const std::byte> data) {
             if (program.AST().Enables().Any(IsUnsupported)) {
                 return;
             }
diff --git a/src/tint/cmd/fuzz/wgsl/fuzz.cc b/src/tint/cmd/fuzz/wgsl/fuzz.cc
index 23bbe0f..17c2e1c 100644
--- a/src/tint/cmd/fuzz/wgsl/fuzz.cc
+++ b/src/tint/cmd/fuzz/wgsl/fuzz.cc
@@ -67,7 +67,7 @@
     Fuzzers().Push(fuzzer);
 }
 
-void Run(std::string_view wgsl, Slice<const std::byte> data, const Options& options) {
+void Run(std::string_view wgsl, const Options& options, Slice<const std::byte> data) {
     tint::SetInternalCompilerErrorReporter(&TintInternalCompilerErrorReporter);
 
 #if TINT_BUILD_WGSL_WRITER
@@ -116,7 +116,7 @@
                 if (options.verbose) {
                     std::cout << " • [" << i << "] Running: " << currently_running << std::endl;
                 }
-                fuzzer.fn(program, data);
+                fuzzer.fn(program, options, data);
             }));
         }
         for (auto& thread : threads) {
@@ -133,7 +133,7 @@
             if (options.verbose) {
                 std::cout << " • Running: " << currently_running << std::endl;
             }
-            fuzzer.fn(program, data);
+            fuzzer.fn(program, options, data);
         }
     }
 }
diff --git a/src/tint/cmd/fuzz/wgsl/fuzz.h b/src/tint/cmd/fuzz/wgsl/fuzz.h
index e4bc7a0..d128695 100644
--- a/src/tint/cmd/fuzz/wgsl/fuzz.h
+++ b/src/tint/cmd/fuzz/wgsl/fuzz.h
@@ -37,20 +37,66 @@
 #include "src/tint/utils/bytes/decoder.h"
 #include "src/tint/utils/containers/slice.h"
 #include "src/tint/utils/macros/static_init.h"
-#include "src/tint/utils/reflection/reflection.h"
 
 namespace tint::fuzz::wgsl {
 
+/// Options for Run()
+struct Options {
+    /// If not empty, only run the fuzzers with the given substring.
+    std::string filter;
+    /// If true, the fuzzers will be run concurrently on separate threads.
+    bool run_concurrently = false;
+    /// If true, print the fuzzer name to stdout before running.
+    bool verbose = false;
+    /// If not empty, load DXC from this path when fuzzing HLSL generation, and fail the fuzzer if
+    /// not found, or if DXC fails to compile.
+    std::string dxc;
+};
+
 /// ProgramFuzzer describes a fuzzer function that takes a WGSL program as input
 struct ProgramFuzzer {
     /// @param name the name of the fuzzer
-    /// @param fn the fuzzer function
-    /// @returns a ProgramFuzzer that invokes the function @p fn with the Program, along with any
-    /// additional arguments which are deserialized from the fuzzer input.
+    /// @param fn the fuzzer function with the signature `void(const Program&, const Options&, ...)`
+    /// @returns a ProgramFuzzer that invokes the function @p fn with the Program, Options, along
+    /// with any additional arguments which are deserialized from the fuzzer input.
+    template <typename... ARGS>
+    static ProgramFuzzer Create(std::string_view name,
+                                void (*fn)(const Program&, const Options&, ARGS...)) {
+        if constexpr (sizeof...(ARGS) > 0) {
+            auto fn_with_decode = [fn](const Program& program, const Options& options,
+                                       Slice<const std::byte> data) {
+                if (!data.data) {
+                    return;
+                }
+                bytes::BufferReader reader{data};
+                auto data_args = bytes::Decode<std::tuple<std::decay_t<ARGS>...>>(reader);
+                if (data_args == Success) {
+                    auto all_args =
+                        std::tuple_cat(std::tuple<const Program&, const Options&>{program, options},
+                                       data_args.Get());
+                    std::apply(*fn, all_args);
+                }
+            };
+            return ProgramFuzzer{name, std::move(fn_with_decode)};
+        } else {
+            return ProgramFuzzer{
+                name,
+                [fn](const Program& program, const Options& options, Slice<const std::byte>) {
+                    fn(program, options);
+                },
+            };
+        }
+    }
+
+    /// @param name the name of the fuzzer
+    /// @param fn the fuzzer function with the signature `void(const Program&, ...)`
+    /// @returns a ProgramFuzzer that invokes the function @p fn with the Program, along
+    /// with any additional arguments which are deserialized from the fuzzer input.
     template <typename... ARGS>
     static ProgramFuzzer Create(std::string_view name, void (*fn)(const Program&, ARGS...)) {
         if constexpr (sizeof...(ARGS) > 0) {
-            auto fn_with_decode = [fn](const Program& program, Slice<const std::byte> data) {
+            auto fn_with_decode = [fn](const Program& program, const Options&,
+                                       Slice<const std::byte> data) {
                 if (!data.data) {
                     return;
                 }
@@ -66,7 +112,9 @@
         } else {
             return ProgramFuzzer{
                 name,
-                [fn](const Program& program, Slice<const std::byte>) { fn(program); },
+                [fn](const Program& program, const Options&, Slice<const std::byte>) {
+                    fn(program);
+                },
             };
         }
     }
@@ -74,30 +122,26 @@
     /// Name of the fuzzer function
     std::string_view name;
     /// The fuzzer function
-    std::function<void(const Program&, Slice<const std::byte> data)> fn;
-};
-
-/// Options for Run()
-struct Options {
-    /// If not empty, only run the fuzzers with the given substring.
-    std::string filter;
-    /// If true, the fuzzers will be run concurrently on separate threads.
-    bool run_concurrently = false;
-    /// If true, print the fuzzer name to stdout before running.
-    bool verbose = false;
+    std::function<void(const Program&, const Options&, Slice<const std::byte> data)> fn;
 };
 
 /// Runs all the registered WGSL fuzzers with the supplied WGSL
 /// @param wgsl the input WGSL
-/// @param data additional data used for fuzzing
 /// @param options the options for running the fuzzers
-void Run(std::string_view wgsl, Slice<const std::byte> data, const Options& options);
+/// @param data additional data used for fuzzing
+void Run(std::string_view wgsl, const Options& options, Slice<const std::byte> data);
 
 /// Registers the fuzzer function with the WGSL fuzzer executable.
 /// @param fuzzer the fuzzer
 void Register(const ProgramFuzzer& fuzzer);
 
 /// TINT_WGSL_PROGRAM_FUZZER registers the fuzzer function to run as part of `tint_wgsl_fuzzer`
+/// The function must have one of the signatures:
+/// • `void(const Program&, ...)`
+/// • `void(const Program&, const Options&, ...)`
+/// Where `...` is any number of deserializable parameters which are decoded from the base64
+/// content of the WGSL comments.
+/// @see bytes::Decode()
 #define TINT_WGSL_PROGRAM_FUZZER(FUNCTION)         \
     TINT_STATIC_INIT(::tint::fuzz::wgsl::Register( \
         ::tint::fuzz::wgsl::ProgramFuzzer::Create(#FUNCTION, FUNCTION)))
diff --git a/src/tint/cmd/fuzz/wgsl/main_fuzz.cc b/src/tint/cmd/fuzz/wgsl/main_fuzz.cc
index 3420d40..06489f1 100644
--- a/src/tint/cmd/fuzz/wgsl/main_fuzz.cc
+++ b/src/tint/cmd/fuzz/wgsl/main_fuzz.cc
@@ -26,6 +26,8 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <iostream>
+#include <string>
+#include <unordered_map>
 
 #include "src/tint/cmd/fuzz/wgsl/fuzz.h"
 #include "src/tint/utils/cli/cli.h"
@@ -42,7 +44,7 @@
     if (size > 0) {
         std::string_view wgsl(reinterpret_cast<const char*>(input), size);
         auto data = tint::DecodeBase64FromComments(wgsl);
-        tint::fuzz::wgsl::Run(wgsl, data.Slice(), options);
+        tint::fuzz::wgsl::Run(wgsl, options, data.Slice());
     }
     return 0;
 }
@@ -76,6 +78,7 @@
         opts.Add<tint::cli::BoolOption>("concurrent", "runs the fuzzers concurrently");
     auto& opt_verbose =
         opts.Add<tint::cli::BoolOption>("verbose", "prints the name of each fuzzer before running");
+    auto& opt_dxc = opts.Add<tint::cli::StringOption>("dxc", "path to DXC DLL");
 
     tint::cli::ParseOptions parse_opts;
     parse_opts.ignore_unknown = true;
@@ -93,5 +96,6 @@
     options.filter = opt_filter.value.value_or("");
     options.run_concurrently = opt_concurrent.value.value_or(false);
     options.verbose = opt_verbose.value.value_or(false);
+    options.dxc = opt_dxc.value.value_or("");
     return 0;
 }
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index 24ef2fd..f413485 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -43,7 +43,7 @@
 #include "src/tint/api/tint.h"
 #include "src/tint/cmd/common/generate_external_texture_bindings.h"
 #include "src/tint/cmd/common/helper.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/wgsl/ast/module.h"
 #include "src/tint/lang/wgsl/ast/transform/first_index_offset.h"
@@ -1009,8 +1009,9 @@
         tint::hlsl::validate::Result dxc_res;
         bool dxc_found = false;
         if (options.validate || must_validate_dxc) {
-            auto dxc = tint::Command::LookPath(
-                options.dxc_path.empty() ? "dxc" : std::string(options.dxc_path));
+            auto dxc =
+                tint::Command::LookPath(options.dxc_path.empty() ? tint::hlsl::validate::kDxcDLLName
+                                                                 : std::string(options.dxc_path));
             if (dxc.Found()) {
                 dxc_found = true;
 
@@ -1053,7 +1054,7 @@
                 fxc_res.output = "FXC DLL '" + options.fxc_path + "' not found. Cannot validate";
             }
 #else
-            if (must_validate_dxc) {
+            if (must_validate_fxc) {
                 fxc_res.failed = true;
                 fxc_res.output = "FXC can only be used on Windows.";
             }
@@ -1222,7 +1223,7 @@
         std::cerr << "Failed to build IR from program: " << result.Failure() << "\n";
         return false;
     }
-    options.printer->Print(tint::core::ir::Disassemble(result.Get()));
+    options.printer->Print(tint::core::ir::Disassemble(result.Get()).Text());
     options.printer->Print(tint::StyledText{} << "\n");
     return true;
 #endif
diff --git a/src/tint/externals.json b/src/tint/externals.json
index dbdf96d..8b8bd96 100644
--- a/src/tint/externals.json
+++ b/src/tint/externals.json
@@ -6,7 +6,19 @@
     "abseil": {
         "IncludePatterns": [
             "absl/**"
+        ]
+    },
+    "dl": {
+        "IncludePatterns": [
+            "dlfcn.h"
         ],
+        "Condition": "tint_build_hlsl_writer"
+    },
+    "dxc-include": {
+        "IncludePatterns": [
+            "dxc/**"
+        ],
+        "Condition": "tint_build_hlsl_writer"
     },
     "google-benchmark": {
         "IncludePatterns": [
diff --git a/src/tint/lang/core/constant/eval.cc b/src/tint/lang/core/constant/eval.cc
index 0b78386..36069fd 100644
--- a/src/tint/lang/core/constant/eval.cc
+++ b/src/tint/lang/core/constant/eval.cc
@@ -281,7 +281,7 @@
             // [abstract-numeric -> x] - materialization failure
             auto msg = OverflowErrorMessage(scalar->value, target_ty->FriendlyName());
             if (ctx.use_runtime_semantics) {
-                ctx.diags.AddWarning(tint::diag::System::Resolver, ctx.source) << msg;
+                ctx.diags.AddWarning(ctx.source) << msg;
                 switch (conv.Failure()) {
                     case ConversionFailure::kExceedsNegativeLimit:
                         return ctx.mgr.Get<Scalar<TO>>(target_ty, TO::Lowest());
@@ -289,7 +289,7 @@
                         return ctx.mgr.Get<Scalar<TO>>(target_ty, TO::Highest());
                 }
             } else {
-                ctx.diags.AddError(tint::diag::System::Resolver, ctx.source) << msg;
+                ctx.diags.AddError(ctx.source) << msg;
                 return nullptr;
             }
         } else if constexpr (IsFloatingPoint<TO>) {
@@ -297,7 +297,7 @@
             // https://www.w3.org/TR/WGSL/#floating-point-conversion
             auto msg = OverflowErrorMessage(scalar->value, target_ty->FriendlyName());
             if (ctx.use_runtime_semantics) {
-                ctx.diags.AddWarning(tint::diag::System::Resolver, ctx.source) << msg;
+                ctx.diags.AddWarning(ctx.source) << msg;
                 switch (conv.Failure()) {
                     case ConversionFailure::kExceedsNegativeLimit:
                         return ctx.mgr.Get<Scalar<TO>>(target_ty, TO::Lowest());
@@ -305,7 +305,7 @@
                         return ctx.mgr.Get<Scalar<TO>>(target_ty, TO::Highest());
                 }
             } else {
-                ctx.diags.AddError(tint::diag::System::Resolver, ctx.source) << msg;
+                ctx.diags.AddError(ctx.source) << msg;
                 return nullptr;
             }
         } else if constexpr (IsFloatingPoint<FROM>) {
@@ -4014,18 +4014,18 @@
 
 diag::Diagnostic& Eval::AddError(const Source& source) const {
     if (use_runtime_semantics_) {
-        return diags.AddWarning(diag::System::Constant, source);
+        return diags.AddWarning(source);
     } else {
-        return diags.AddError(diag::System::Constant, source);
+        return diags.AddError(source);
     }
 }
 
 diag::Diagnostic& Eval::AddWarning(const Source& source) const {
-    return diags.AddWarning(diag::System::Constant, source);
+    return diags.AddWarning(source);
 }
 
 diag::Diagnostic& Eval::AddNote(const Source& source) const {
-    return diags.AddNote(diag::System::Constant, source);
+    return diags.AddNote(source);
 }
 
 }  // namespace tint::core::constant
diff --git a/src/tint/lang/core/ir/BUILD.bazel b/src/tint/lang/core/ir/BUILD.bazel
index a02930e..6617663 100644
--- a/src/tint/lang/core/ir/BUILD.bazel
+++ b/src/tint/lang/core/ir/BUILD.bazel
@@ -57,7 +57,7 @@
     "core_binary.cc",
     "core_builtin_call.cc",
     "core_unary.cc",
-    "disassembler.cc",
+    "disassembly.cc",
     "discard.cc",
     "exit.cc",
     "exit_if.cc",
@@ -109,7 +109,7 @@
     "core_binary.h",
     "core_builtin_call.h",
     "core_unary.h",
-    "disassembler.h",
+    "disassembly.h",
     "discard.h",
     "exit.h",
     "exit_if.h",
diff --git a/src/tint/lang/core/ir/BUILD.cmake b/src/tint/lang/core/ir/BUILD.cmake
index 94365b4..70bc0d4 100644
--- a/src/tint/lang/core/ir/BUILD.cmake
+++ b/src/tint/lang/core/ir/BUILD.cmake
@@ -78,8 +78,8 @@
   lang/core/ir/core_builtin_call.h
   lang/core/ir/core_unary.cc
   lang/core/ir/core_unary.h
-  lang/core/ir/disassembler.cc
-  lang/core/ir/disassembler.h
+  lang/core/ir/disassembly.cc
+  lang/core/ir/disassembly.h
   lang/core/ir/discard.cc
   lang/core/ir/discard.h
   lang/core/ir/exit.cc
diff --git a/src/tint/lang/core/ir/BUILD.gn b/src/tint/lang/core/ir/BUILD.gn
index ac59268..9927e42 100644
--- a/src/tint/lang/core/ir/BUILD.gn
+++ b/src/tint/lang/core/ir/BUILD.gn
@@ -80,8 +80,8 @@
     "core_builtin_call.h",
     "core_unary.cc",
     "core_unary.h",
-    "disassembler.cc",
-    "disassembler.h",
+    "disassembly.cc",
+    "disassembly.h",
     "discard.cc",
     "discard.h",
     "exit.cc",
diff --git a/src/tint/lang/core/ir/binary/roundtrip_fuzz.cc b/src/tint/lang/core/ir/binary/roundtrip_fuzz.cc
index ccc7a9c..7378a3f 100644
--- a/src/tint/lang/core/ir/binary/roundtrip_fuzz.cc
+++ b/src/tint/lang/core/ir/binary/roundtrip_fuzz.cc
@@ -28,7 +28,7 @@
 #include "src/tint/cmd/fuzz/ir/fuzz.h"
 #include "src/tint/lang/core/ir/binary/decode.h"
 #include "src/tint/lang/core/ir/binary/encode.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 
 namespace tint::core::ir::binary {
 namespace {
diff --git a/src/tint/lang/core/ir/binary/roundtrip_test.cc b/src/tint/lang/core/ir/binary/roundtrip_test.cc
index 9d8b368..52a516b 100644
--- a/src/tint/lang/core/ir/binary/roundtrip_test.cc
+++ b/src/tint/lang/core/ir/binary/roundtrip_test.cc
@@ -29,7 +29,7 @@
 
 #include "src/tint/lang/core/ir/binary/decode.h"
 #include "src/tint/lang/core/ir/binary/encode.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/core/type/depth_multisampled_texture.h"
 #include "src/tint/lang/core/type/depth_texture.h"
 #include "src/tint/lang/core/type/external_texture.h"
diff --git a/src/tint/lang/core/ir/disassembler.cc b/src/tint/lang/core/ir/disassembly.cc
similarity index 84%
rename from src/tint/lang/core/ir/disassembler.cc
rename to src/tint/lang/core/ir/disassembly.cc
index 1664dce..a07033f 100644
--- a/src/tint/lang/core/ir/disassembler.cc
+++ b/src/tint/lang/core/ir/disassembly.cc
@@ -25,32 +25,26 @@
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
+#include <memory>
+#include <string_view>
 
 #include "src//tint/lang/core/ir/unary.h"
 #include "src/tint/lang/core/constant/composite.h"
 #include "src/tint/lang/core/constant/scalar.h"
 #include "src/tint/lang/core/constant/splat.h"
-#include "src/tint/lang/core/fluent_types.h"
-#include "src/tint/lang/core/ir/access.h"
 #include "src/tint/lang/core/ir/binary.h"
-#include "src/tint/lang/core/ir/bitcast.h"
 #include "src/tint/lang/core/ir/block.h"
 #include "src/tint/lang/core/ir/block_param.h"
 #include "src/tint/lang/core/ir/break_if.h"
-#include "src/tint/lang/core/ir/construct.h"
 #include "src/tint/lang/core/ir/continue.h"
-#include "src/tint/lang/core/ir/convert.h"
-#include "src/tint/lang/core/ir/core_builtin_call.h"
 #include "src/tint/lang/core/ir/discard.h"
 #include "src/tint/lang/core/ir/exit_if.h"
 #include "src/tint/lang/core/ir/exit_loop.h"
 #include "src/tint/lang/core/ir/exit_switch.h"
+#include "src/tint/lang/core/ir/function.h"
 #include "src/tint/lang/core/ir/if.h"
 #include "src/tint/lang/core/ir/instruction_result.h"
-#include "src/tint/lang/core/ir/let.h"
-#include "src/tint/lang/core/ir/load.h"
-#include "src/tint/lang/core/ir/load_vector_element.h"
 #include "src/tint/lang/core/ir/loop.h"
 #include "src/tint/lang/core/ir/multi_in_block.h"
 #include "src/tint/lang/core/ir/next_iteration.h"
@@ -67,7 +61,6 @@
 #include "src/tint/lang/core/type/type.h"
 #include "src/tint/utils/ice/ice.h"
 #include "src/tint/utils/macros/defer.h"
-#include "src/tint/utils/macros/scoped_assignment.h"
 #include "src/tint/utils/rtti/switch.h"
 #include "src/tint/utils/text/string.h"
 #include "src/tint/utils/text/styled_text.h"
@@ -104,85 +97,29 @@
 
 }  // namespace
 
-StyledText Disassemble(const Module& mod) {
-    return Disassembler{mod}.Disassemble();
-}
+Disassembly::Disassembly(Disassembly&&) = default;
 
-Disassembler::Disassembler(const Module& mod) : mod_(mod) {}
+Disassembly::Disassembly(const Module& mod, std::string_view file_name) : mod_(mod) {
+    Disassemble();
+    file_ = std::make_shared<Source::File>(std::string(file_name), Plain());
 
-Disassembler::~Disassembler() = default;
-
-StyledText& Disassembler::Indent() {
-    for (uint32_t i = 0; i < indent_size_; i++) {
-        out_ << " ";
-    }
-    return out_;
-}
-
-void Disassembler::EmitLine() {
-    out_ << "\n";
-    current_output_line_ += 1;
-    current_output_start_pos_ = static_cast<uint32_t>(out_.Length());
-}
-
-size_t Disassembler::IdOf(const Block* node) {
-    TINT_ASSERT(node);
-    return block_ids_.GetOrAdd(node, [&] { return block_ids_.Count(); });
-}
-
-std::string Disassembler::IdOf(const Value* value) {
-    TINT_ASSERT(value);
-    return value_ids_.GetOrAdd(value, [&] {
-        if (auto sym = mod_.NameOf(value)) {
-            if (ids_.Add(sym.Name())) {
-                return sym.Name();
-            }
-            auto prefix = sym.Name() + "_";
-            for (size_t i = 1;; i++) {
-                auto name = prefix + std::to_string(i);
-                if (ids_.Add(name)) {
-                    return name;
-                }
-            }
+    auto set_source_file = [&](auto& map) {
+        for (auto& it : map) {
+            it.value.file = file_.get();
         }
-        return std::to_string(value_ids_.Count());
-    });
-}
-
-std::string Disassembler::NameOf(const If* inst) {
-    if (!inst) {
-        return "undef";
-    }
-
-    return if_names_.GetOrAdd(inst, [&] { return "if_" + std::to_string(if_names_.Count()); });
-}
-
-std::string Disassembler::NameOf(const Loop* inst) {
-    if (!inst) {
-        return "undef";
-    }
-
-    return loop_names_.GetOrAdd(inst,
-                                [&] { return "loop_" + std::to_string(loop_names_.Count()); });
-}
-
-std::string Disassembler::NameOf(const Switch* inst) {
-    if (!inst) {
-        return "undef";
-    }
-
-    return switch_names_.GetOrAdd(
-        inst, [&] { return "switch_" + std::to_string(switch_names_.Count()); });
-}
-
-Source::Location Disassembler::MakeCurrentLocation() {
-    return Source::Location{
-        current_output_line_,
-        static_cast<uint32_t>(out_.Length()) - current_output_start_pos_ + 1,
     };
+    set_source_file(block_to_src_);
+    set_source_file(block_param_to_src_);
+    set_source_file(instruction_to_src_);
+    set_source_file(operand_to_src_);
+    set_source_file(result_to_src_);
+    set_source_file(function_to_src_);
+    set_source_file(function_param_to_src_);
 }
 
-const StyledText& Disassembler::Disassemble() {
+Disassembly::~Disassembly() = default;
+
+void Disassembly::Disassemble() {
     TINT_DEFER(out_ << StylePlain);
     out_.Clear();
     out_ << StyleCode;
@@ -201,14 +138,33 @@
     for (auto& func : mod_.functions) {
         EmitFunction(func);
     }
+}
+
+StyledText& Disassembly::Indent() {
+    for (uint32_t i = 0; i < indent_size_; i++) {
+        out_ << " ";
+    }
     return out_;
 }
 
-void Disassembler::EmitBlock(const Block* blk, std::string_view comment /* = "" */) {
+void Disassembly::EmitLine() {
+    out_ << "\n";
+    current_output_line_ += 1;
+    current_output_start_pos_ = static_cast<uint32_t>(out_.Length());
+}
+
+Source::Location Disassembly::MakeCurrentLocation() {
+    return Source::Location{
+        current_output_line_,
+        static_cast<uint32_t>(out_.Length()) - current_output_start_pos_ + 1,
+    };
+}
+
+void Disassembly::EmitBlock(const Block* blk, std::string_view comment /* = "" */) {
     Indent();
 
     SourceMarker sm(this);
-    out_ << StyleLabel("$B", IdOf(blk));
+    out_ << NameOf(blk);
     if (auto* merge = blk->As<MultiInBlock>()) {
         if (!merge->Params().IsEmpty()) {
             out_ << " (";
@@ -216,9 +172,12 @@
                 if (p != merge->Params().Front()) {
                     out_ << ", ";
                 }
-                SourceMarker psm(this);
-                EmitValue(p);
-                psm.Store(p);
+                {
+                    SourceMarker psm(this);
+                    EmitValue(p);
+                    psm.Store(p);
+                }
+                out_ << ":" << StyleType(p->Type()->FriendlyName());
             }
             out_ << ")";
         }
@@ -243,12 +202,12 @@
     EmitLine();
 }
 
-void Disassembler::EmitBindingPoint(BindingPoint p) {
+void Disassembly::EmitBindingPoint(BindingPoint p) {
     out_ << StyleAttribute("@binding_point") << "(" << StyleLiteral(p.group) << ", "
          << StyleLiteral(p.binding) << ")";
 }
 
-void Disassembler::EmitLocation(Location loc) {
+void Disassembly::EmitLocation(Location loc) {
     out_ << StyleAttribute("@location") << "(" << loc.value << ")";
     if (loc.interpolation.has_value()) {
         out_ << ", " << StyleAttribute("@interpolate") << "(";
@@ -261,7 +220,7 @@
     }
 }
 
-void Disassembler::EmitParamAttributes(const FunctionParam* p) {
+void Disassembly::EmitParamAttributes(const FunctionParam* p) {
     if (!p->Invariant() && !p->Location().has_value() && !p->BindingPoint().has_value() &&
         !p->Builtin().has_value()) {
         return;
@@ -298,7 +257,7 @@
     out_ << "]";
 }
 
-void Disassembler::EmitReturnAttributes(const Function* func) {
+void Disassembly::EmitReturnAttributes(const Function* func) {
     if (!func->ReturnInvariant() && !func->ReturnLocation().has_value() &&
         !func->ReturnBuiltin().has_value()) {
         return;
@@ -330,13 +289,13 @@
     out_ << "]";
 }
 
-void Disassembler::EmitFunction(const Function* func) {
+void Disassembly::EmitFunction(const Function* func) {
     in_function_ = true;
 
-    std::string fn_id = IdOf(func);
+    auto fn_id = NameOf(func);
     {
         SourceMarker sm(this);
-        Indent() << StyleFunction("%", fn_id);
+        Indent() << fn_id;
         sm.Store(func);
     }
     out_ << " =";
@@ -357,7 +316,7 @@
             out_ << ", ";
         }
         SourceMarker sm(this);
-        out_ << StyleVariable("%", IdOf(p)) << ":" << StyleType(p->Type()->FriendlyName());
+        out_ << NameOf(p) << ":" << StyleType(p->Type()->FriendlyName());
         sm.Store(p);
 
         EmitParamAttributes(p);
@@ -371,15 +330,15 @@
     {  // Add a comment if the function IDs or parameter IDs doesn't match their name
         Vector<std::string, 4> names;
         if (auto name = mod_.NameOf(func); name.IsValid()) {
-            if (name.NameView() != fn_id) {
-                names.Push("%" + std::string(fn_id) + ": '" + name.Name() + "'");
+            if ("%" + name.Name() != fn_id.Plain()) {
+                names.Push(fn_id.Plain() + ": '" + name.Name() + "'");
             }
         }
         for (auto* p : func->Params()) {
             if (auto name = mod_.NameOf(p); name.IsValid()) {
-                auto id = IdOf(p);
-                if (name.NameView() != id) {
-                    names.Push("%" + std::string(id) + ": '" + name.Name() + "'");
+                auto id = NameOf(p);
+                if ("%" + name.Name() != id.Plain()) {
+                    names.Push(id.Plain() + ": '" + name.Name() + "'");
                 }
             }
         }
@@ -398,27 +357,24 @@
     EmitLine();
 }
 
-void Disassembler::EmitValueWithType(const Instruction* val) {
+void Disassembly::EmitValueWithType(const Instruction* val) {
     SourceMarker sm(this);
-    if (val->Result(0)) {
-        EmitValueWithType(val->Result(0));
-    } else {
-        out_ << "undef";
-    }
+    EmitValueWithType(val->Result(0));
     sm.StoreResult(IndexedValue{val, 0});
 }
 
-void Disassembler::EmitValueWithType(const Value* val) {
-    if (!val) {
-        out_ << "undef";
-        return;
-    }
-
+void Disassembly::EmitValueWithType(const Value* val) {
     EmitValue(val);
-    out_ << ":" << StyleType(val->Type()->FriendlyName());
+    if (val) {
+        out_ << ":" << StyleType(val->Type()->FriendlyName());
+    }
 }
 
-void Disassembler::EmitValue(const Value* val) {
+void Disassembly::EmitValue(const Value* val) {
+    if (!val) {
+        out_ << StyleLiteral("undef");
+        return;
+    }
     tint::Switch(
         val,
         [&](const ir::Constant* constant) {
@@ -467,28 +423,16 @@
                 };
             emit(constant->Value());
         },
-        [&](const ir::InstructionResult* rv) { out_ << StyleVariable("%", IdOf(rv)); },
-        [&](const ir::BlockParam* p) {
-            out_ << StyleVariable("%", IdOf(p)) << ":" << StyleType(p->Type()->FriendlyName());
-        },
-        [&](const ir::FunctionParam* p) { out_ << StyleVariable("%", IdOf(p)); },
-        [&](const ir::Function* f) { out_ << StyleVariable("%", IdOf(f)); },
-        [&](Default) {
-            if (val == nullptr) {
-                out_ << StyleVariable("undef");
-            } else {
-                out_ << StyleError("unknown value: ", val->TypeInfo().name);
-            }
-        });
+        [&](Default) { out_ << NameOf(val); });
 }
 
-void Disassembler::EmitInstructionName(const Instruction* inst) {
+void Disassembly::EmitInstructionName(const Instruction* inst) {
     SourceMarker sm(this);
     out_ << StyleInstruction(inst->FriendlyName());
     sm.Store(inst);
 }
 
-void Disassembler::EmitInstruction(const Instruction* inst) {
+void Disassembly::EmitInstruction(const Instruction* inst) {
     TINT_DEFER(EmitLine());
 
     if (!inst->Alive()) {
@@ -604,9 +548,9 @@
         for (auto* result : inst->Results()) {
             if (result) {
                 if (auto name = mod_.NameOf(result); name.IsValid()) {
-                    auto id = IdOf(result);
-                    if (name.NameView() != id) {
-                        names.Push("%" + std::string(id) + ": '" + name.Name() + "'");
+                    auto id = NameOf(result).Plain();
+                    if ("%" + name.Name() != id) {
+                        names.Push(id + ": '" + name.Name() + "'");
                     }
                 }
             }
@@ -617,13 +561,13 @@
     }
 }
 
-void Disassembler::EmitOperand(const Instruction* inst, size_t index) {
+void Disassembly::EmitOperand(const Instruction* inst, size_t index) {
     SourceMarker marker(this);
     EmitValue(inst->Operands()[index]);
     marker.Store(IndexedValue{inst, static_cast<uint32_t>(index)});
 }
 
-void Disassembler::EmitOperandList(const Instruction* inst, size_t start_index /* = 0 */) {
+void Disassembly::EmitOperandList(const Instruction* inst, size_t start_index /* = 0 */) {
     for (size_t i = start_index, n = inst->Operands().Length(); i < n; i++) {
         if (i != start_index) {
             out_ << ", ";
@@ -632,7 +576,7 @@
     }
 }
 
-void Disassembler::EmitIf(const If* if_) {
+void Disassembly::EmitIf(const If* if_) {
     SourceMarker sm(this);
     if (auto results = if_->Results(); !results.IsEmpty()) {
         for (size_t i = 0; i < results.Length(); ++i) {
@@ -650,9 +594,9 @@
 
     bool has_false = !if_->False()->IsEmpty();
 
-    out_ << " [" << StyleKeyword("t") << ": " << StyleLabel("$B", IdOf(if_->True()));
+    out_ << " [" << StyleKeyword("t") << ": " << NameOf(if_->True());
     if (has_false) {
-        out_ << ", " << StyleKeyword("f") << ": " << StyleLabel("$B", IdOf(if_->False()));
+        out_ << ", " << StyleKeyword("f") << ": " << NameOf(if_->False());
     }
     out_ << "]";
     sm.Store(if_);
@@ -683,7 +627,7 @@
     out_ << "}";
 }
 
-void Disassembler::EmitLoop(const Loop* l) {
+void Disassembly::EmitLoop(const Loop* l) {
     SourceMarker sm(this);
     if (auto results = l->Results(); !results.IsEmpty()) {
         for (size_t i = 0; i < results.Length(); ++i) {
@@ -699,17 +643,15 @@
     out_ << StyleInstruction("loop") << " [";
 
     if (!l->Initializer()->IsEmpty()) {
-        out_ << StyleKeyword("i") << ": "
-             << StyleLabel("$B", std::to_string(IdOf(l->Initializer())));
+        out_ << StyleKeyword("i") << ": " << NameOf(l->Initializer());
         out_ << ", ";
     }
 
-    out_ << StyleKeyword("b") << ": " << StyleLabel("$B", std::to_string(IdOf(l->Body())));
+    out_ << StyleKeyword("b") << ": " << NameOf(l->Body());
 
     if (!l->Continuing()->IsEmpty()) {
         out_ << ", ";
-        out_ << StyleKeyword("c") << ": "
-             << StyleLabel("$B", std::to_string(IdOf(l->Continuing())));
+        out_ << StyleKeyword("c") << ": " << NameOf(l->Continuing());
     }
 
     out_ << "]";
@@ -738,7 +680,7 @@
     out_ << "}";
 }
 
-void Disassembler::EmitSwitch(const Switch* s) {
+void Disassembly::EmitSwitch(const Switch* s) {
     SourceMarker sm(this);
     if (auto results = s->Results(); !results.IsEmpty()) {
         for (size_t i = 0; i < results.Length(); ++i) {
@@ -770,7 +712,7 @@
                 EmitValue(selector.val);
             }
         }
-        out_ << ", " << StyleLabel("$B", IdOf(c.block)) << ")";
+        out_ << ", " << NameOf(c.block) << ")";
     }
     out_ << "]";
     sm.Store(s);
@@ -787,7 +729,7 @@
     out_ << "}";
 }
 
-void Disassembler::EmitTerminator(const Terminator* b) {
+void Disassembly::EmitTerminator(const Terminator* b) {
     SourceMarker sm(this);
     size_t args_offset = 0;
     tint::Switch(
@@ -835,11 +777,11 @@
         b,  //
         [&](const ir::BreakIf* bi) {
             out_ << "  "
-                 << StyleComment("# -> [t: exit_loop ", NameOf(bi->Loop()), ", f: $B",
-                                 IdOf(bi->Loop()->Body()), "]");
+                 << StyleComment("# -> [t: exit_loop ", NameOf(bi->Loop()),
+                                 ", f: ", NameOf(bi->Loop()->Body()), "]");
         },
         [&](const ir::Continue* c) {
-            out_ << "  " << StyleComment("# -> $B", IdOf(c->Loop()->Continuing()));
+            out_ << "  " << StyleComment("# -> ", NameOf(c->Loop()->Continuing()));
         },                                                                                  //
         [&](const ir::ExitIf* e) { out_ << "  " << StyleComment("# ", NameOf(e->If())); },  //
         [&](const ir::ExitSwitch* e) {
@@ -847,11 +789,11 @@
         },                                                                                      //
         [&](const ir::ExitLoop* e) { out_ << "  " << StyleComment("# ", NameOf(e->Loop())); },  //
         [&](const ir::NextIteration* ni) {
-            out_ << "  " << StyleComment("# -> $B", IdOf(ni->Loop()->Body()));
+            out_ << "  " << StyleComment("# -> ", NameOf(ni->Loop()->Body()));
         });
 }
 
-void Disassembler::EmitBinary(const Binary* b) {
+void Disassembly::EmitBinary(const Binary* b) {
     SourceMarker sm(this);
     EmitValueWithType(b);
     out_ << " = ";
@@ -917,7 +859,7 @@
     sm.Store(b);
 }
 
-void Disassembler::EmitUnary(const Unary* u) {
+void Disassembly::EmitUnary(const Unary* u) {
     SourceMarker sm(this);
     EmitValueWithType(u);
     out_ << " = ";
@@ -944,7 +886,7 @@
     sm.Store(u);
 }
 
-void Disassembler::EmitStructDecl(const core::type::Struct* str) {
+void Disassembly::EmitStructDecl(const core::type::Struct* str) {
     out_ << StyleType(str->Name().Name()) << " = " << StyleKeyword("struct") << " "
          << StyleAttribute("@align") << "(" << StyleLiteral(str->Align()) << ")";
     if (str->StructFlags().Contains(core::type::StructFlag::kBlock)) {
@@ -982,4 +924,64 @@
     EmitLine();
 }
 
+StyledText Disassembly::NameOf(const Block* node) {
+    TINT_ASSERT(node);
+    auto id = block_ids_.GetOrAdd(node, [&] { return block_ids_.Count(); });
+    return StyledText{} << StyleLabel("$B", id);
+}
+
+StyledText Disassembly::NameOf(const Value* value) {
+    TINT_ASSERT(value);
+    auto id = value_ids_.GetOrAdd(value, [&] {
+        if (auto sym = mod_.NameOf(value)) {
+            if (ids_.Add(sym.Name())) {
+                return sym.Name();
+            }
+            auto prefix = sym.Name() + "_";
+            for (size_t i = 1;; i++) {
+                auto name = prefix + std::to_string(i);
+                if (ids_.Add(name)) {
+                    return name;
+                }
+            }
+        }
+        return std::to_string(value_ids_.Count());
+    });
+
+    auto style = tint::Switch(
+        value,                                           //
+        [&](const Function*) { return StyleFunction; },  //
+        [&](const InstructionResult*) { return StyleVariable; });
+    return StyledText{} << style("%", id);
+}
+
+StyledText Disassembly::NameOf(const If* inst) {
+    if (!inst) {
+        return StyledText{} << StyleError("undef");
+    }
+
+    auto name = if_names_.GetOrAdd(inst, [&] { return "if_" + std::to_string(if_names_.Count()); });
+    return StyledText{} << StyleInstruction(name);
+}
+
+StyledText Disassembly::NameOf(const Loop* inst) {
+    if (!inst) {
+        return StyledText{} << StyleError("undef");
+    }
+
+    auto name =
+        loop_names_.GetOrAdd(inst, [&] { return "loop_" + std::to_string(loop_names_.Count()); });
+    return StyledText{} << StyleInstruction(name);
+}
+
+StyledText Disassembly::NameOf(const Switch* inst) {
+    if (!inst) {
+        return StyledText{} << StyleError("undef");
+    }
+
+    auto name = switch_names_.GetOrAdd(
+        inst, [&] { return "switch_" + std::to_string(switch_names_.Count()); });
+    return StyledText{} << StyleInstruction(name);
+}
+
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/disassembler.h b/src/tint/lang/core/ir/disassembly.h
similarity index 78%
rename from src/tint/lang/core/ir/disassembler.h
rename to src/tint/lang/core/ir/disassembly.h
index df20534..677b386 100644
--- a/src/tint/lang/core/ir/disassembler.h
+++ b/src/tint/lang/core/ir/disassembly.h
@@ -25,10 +25,12 @@
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-#ifndef SRC_TINT_LANG_CORE_IR_DISASSEMBLER_H_
-#define SRC_TINT_LANG_CORE_IR_DISASSEMBLER_H_
+#ifndef SRC_TINT_LANG_CORE_IR_DISASSEMBLY_H_
+#define SRC_TINT_LANG_CORE_IR_DISASSEMBLY_H_
 
+#include <memory>
 #include <string>
+#include <string_view>
 
 #include "src/tint/lang/core/ir/binary.h"
 #include "src/tint/lang/core/ir/block.h"
@@ -40,7 +42,6 @@
 #include "src/tint/lang/core/ir/unary.h"
 #include "src/tint/utils/containers/hashmap.h"
 #include "src/tint/utils/containers/hashset.h"
-#include "src/tint/utils/text/string_stream.h"
 #include "src/tint/utils/text/styled_text.h"
 
 // Forward declarations.
@@ -50,12 +51,8 @@
 
 namespace tint::core::ir {
 
-/// @returns the disassembly for the module @p mod
-/// @param mod the module to disassemble
-StyledText Disassemble(const Module& mod);
-
-/// Helper class to disassemble the IR
-class Disassembler {
+/// Disassembly holds the disassembly of an IR module.
+class Disassembly {
   public:
     /// A reference to an instruction's operand or result.
     struct IndexedValue {
@@ -75,32 +72,63 @@
         }
     };
 
-    /// Constructor
-    /// @param mod the module
-    explicit Disassembler(const Module& mod);
-    ~Disassembler();
+    /// Constructor.
+    /// Performs the disassembly of the module @p mod, constructing a Source::File with the name @p
+    /// file_name.
+    /// @param mod the module to disassemble
+    Disassembly(const Module& mod, std::string_view file_name);
 
-    /// Returns the module as a styled text string
+    /// Move constructor
+    Disassembly(Disassembly&&);
+
+    /// Destructor
+    ~Disassembly();
+
     /// @returns the string representation of the module
-    const StyledText& Disassemble();
+    const StyledText& Text() const { return out_; }
+
+    /// @returns the string representation of the module as plain-text
+    std::string Plain() const { return out_.Plain(); }
+
+    /// @returns the disassembly file
+    const std::shared_ptr<Source::File>& File() const { return file_; }
+
+    /// @returns the disassembled name for the Block @p blk
+    StyledText NameOf(const Block* blk);
+
+    /// @returns the disassembled name for the Value @p node
+    StyledText NameOf(const Value* node);
+
+    /// @returns the disassembled name for the If @p inst
+    StyledText NameOf(const If* inst);
+
+    /// @returns the disassembled name for the Loop @p inst
+    StyledText NameOf(const Loop* inst);
+
+    /// @returns the disassembled name for the Switch @p inst
+    StyledText NameOf(const Switch* inst);
 
     /// @param inst the instruction to retrieve
     /// @returns the source for the instruction
-    Source InstructionSource(const Instruction* inst) {
+    Source InstructionSource(const Instruction* inst) const {
         return instruction_to_src_.GetOr(inst, Source{});
     }
 
     /// @param operand the operand to retrieve
     /// @returns the source for the operand
-    Source OperandSource(IndexedValue operand) { return operand_to_src_.GetOr(operand, Source{}); }
+    Source OperandSource(IndexedValue operand) const {
+        return operand_to_src_.GetOr(operand, Source{});
+    }
 
     /// @param result the result to retrieve
     /// @returns the source for the result
-    Source ResultSource(IndexedValue result) { return result_to_src_.GetOr(result, Source{}); }
+    Source ResultSource(IndexedValue result) const {
+        return result_to_src_.GetOr(result, Source{});
+    }
 
     /// @param blk the block to retrieve
     /// @returns the source for the block
-    Source BlockSource(const Block* blk) { return block_to_src_.GetOr(blk, Source{}); }
+    Source BlockSource(const Block* blk) const { return block_to_src_.GetOr(blk, Source{}); }
 
     /// @param param the block parameter to retrieve
     /// @returns the source for the parameter
@@ -118,6 +146,10 @@
         return function_param_to_src_.GetOr(param, Source{});
     }
 
+  private:
+    /// Performs the disassembling of the module.
+    void Disassemble();
+
     /// Stores the given @p src location for @p inst instruction
     /// @param inst the instruction to store
     /// @param src the source location
@@ -158,10 +190,9 @@
     /// @returns the source location for the current emission location
     Source::Location MakeCurrentLocation();
 
-  private:
     class SourceMarker {
       public:
-        explicit SourceMarker(Disassembler* d) : dis_(d), begin_(dis_->MakeCurrentLocation()) {}
+        explicit SourceMarker(Disassembly* d) : dis_(d), begin_(dis_->MakeCurrentLocation()) {}
         ~SourceMarker() = default;
 
         void Store(const Instruction* inst) { dis_->SetSource(inst, MakeSource()); }
@@ -183,18 +214,12 @@
         }
 
       private:
-        Disassembler* dis_ = nullptr;
+        Disassembly* dis_ = nullptr;
         Source::Location begin_;
     };
 
     StyledText& Indent();
 
-    size_t IdOf(const Block* blk);
-    std::string IdOf(const Value* node);
-    std::string NameOf(const If* inst);
-    std::string NameOf(const Loop* inst);
-    std::string NameOf(const Switch* inst);
-
     void EmitBlock(const Block* blk, std::string_view comment = "");
     void EmitFunction(const Function* func);
     void EmitParamAttributes(const FunctionParam* p);
@@ -219,9 +244,7 @@
 
     const Module& mod_;
     StyledText out_;
-    Hashmap<const Block*, size_t, 32> block_ids_;
-    Hashmap<const Value*, std::string, 32> value_ids_;
-    Hashset<std::string, 32> ids_;
+    std::shared_ptr<Source::File> file_;
     uint32_t indent_size_ = 0;
     bool in_function_ = false;
 
@@ -235,11 +258,21 @@
     Hashmap<IndexedValue, Source, 8> result_to_src_;
     Hashmap<const Function*, Source, 8> function_to_src_;
     Hashmap<const FunctionParam*, Source, 8> function_param_to_src_;
+
+    // Names / IDs
+    Hashmap<const Block*, size_t, 32> block_ids_;
+    Hashmap<const Value*, std::string, 32> value_ids_;
     Hashmap<const If*, std::string, 8> if_names_;
     Hashmap<const Loop*, std::string, 8> loop_names_;
     Hashmap<const Switch*, std::string, 8> switch_names_;
+    Hashset<std::string, 32> ids_;
 };
 
+/// @returns the disassembly for the module @p mod, using the file name @p file_name
+inline Disassembly Disassemble(const Module& mod, std::string_view file_name = "") {
+    return Disassembly(mod, file_name);
+}
+
 }  // namespace tint::core::ir
 
-#endif  // SRC_TINT_LANG_CORE_IR_DISASSEMBLER_H_
+#endif  // SRC_TINT_LANG_CORE_IR_DISASSEMBLY_H_
diff --git a/src/tint/lang/core/ir/function.cc b/src/tint/lang/core/ir/function.cc
index 5b669c2..1138fbf 100644
--- a/src/tint/lang/core/ir/function.cc
+++ b/src/tint/lang/core/ir/function.cc
@@ -87,6 +87,11 @@
     }
 }
 
+void Function::AppendParam(FunctionParam* param) {
+    params_.Push(param);
+    param->SetFunction(this);
+}
+
 void Function::Destroy() {
     Base::Destroy();
     block_->Destroy();
diff --git a/src/tint/lang/core/ir/function.h b/src/tint/lang/core/ir/function.h
index 5cf533f..f7c7ebd 100644
--- a/src/tint/lang/core/ir/function.h
+++ b/src/tint/lang/core/ir/function.h
@@ -146,6 +146,10 @@
     /// @param params the function parameters
     void SetParams(std::initializer_list<FunctionParam*> params);
 
+    /// Appends a new function parameter.
+    /// @param param the function parameter to append
+    void AppendParam(FunctionParam* param);
+
     /// @returns the function parameters
     const VectorRef<FunctionParam*> Params() { return params_; }
 
diff --git a/src/tint/lang/core/ir/function_test.cc b/src/tint/lang/core/ir/function_test.cc
index 27f332e..65a61ba 100644
--- a/src/tint/lang/core/ir/function_test.cc
+++ b/src/tint/lang/core/ir/function_test.cc
@@ -160,6 +160,11 @@
     EXPECT_EQ(param1->Function(), f);
     EXPECT_EQ(param2->Function(), nullptr);
     EXPECT_EQ(param3->Function(), f);
+
+    f->AppendParam(param2);
+    EXPECT_EQ(param1->Function(), f);
+    EXPECT_EQ(param2->Function(), f);
+    EXPECT_EQ(param3->Function(), f);
 }
 
 }  // namespace
diff --git a/src/tint/lang/core/ir/ice.h b/src/tint/lang/core/ir/ice.h
index 6487cc3..cdc3e09 100644
--- a/src/tint/lang/core/ir/ice.h
+++ b/src/tint/lang/core/ir/ice.h
@@ -28,10 +28,9 @@
 #ifndef SRC_TINT_LANG_CORE_IR_ICE_H_
 #define SRC_TINT_LANG_CORE_IR_ICE_H_
 
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 
 /// Emit an ICE message with the disassembly of `mod` attached.
-#define TINT_IR_ICE(mod) \
-    TINT_ICE() << tint::core::ir::Disassembler{mod}.Disassemble().Plain() << "\n"
+#define TINT_IR_ICE(mod) TINT_ICE() << tint::core::ir::Disassemble(mod).Plain() << "\n"
 
 #endif  // SRC_TINT_LANG_CORE_IR_ICE_H_
diff --git a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_test.cc b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_test.cc
index 1e4b39e..7595c2c 100644
--- a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_test.cc
+++ b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill_test.cc
@@ -525,7 +525,7 @@
     auto* coords = b.FunctionParam("coords", ty.vec2<u32>());
     auto* index = b.FunctionParam("index", ty.u32());
     auto* value = b.FunctionParam("value", ty.vec4<f32>());
-    func->SetParams({value, coords});
+    func->SetParams({value, coords, index, value});
     b.Append(func->Block(), [&] {
         auto* load = b.Load(var->Result(0));
         b.Call(ty.void_(), core::BuiltinFn::kTextureStore, load, coords, index, value);
@@ -537,10 +537,10 @@
   %texture:ptr<handle, texture_storage_2d_array<bgra8unorm, write>, read> = var @binding_point(1, 2)
 }
 
-%foo = func(%value:vec4<f32>, %coords:vec2<u32>):void {
+%foo = func(%value:vec4<f32>, %coords:vec2<u32>, %index:u32%value:vec4<f32>):void {
   $B2: {
-    %5:texture_storage_2d_array<bgra8unorm, write> = load %texture
-    %6:void = textureStore %5, %coords, %index, %value
+    %6:texture_storage_2d_array<bgra8unorm, write> = load %texture
+    %7:void = textureStore %6, %coords, %index, %value
     ret
   }
 }
@@ -550,11 +550,11 @@
   %texture:ptr<handle, texture_storage_2d_array<rgba8unorm, write>, read> = var @binding_point(1, 2)
 }
 
-%foo = func(%value:vec4<f32>, %coords:vec2<u32>):void {
+%foo = func(%value:vec4<f32>, %coords:vec2<u32>, %index:u32%value:vec4<f32>):void {
   $B2: {
-    %5:texture_storage_2d_array<rgba8unorm, write> = load %texture
-    %6:vec4<f32> = swizzle %value, zyxw
-    %7:void = textureStore %5, %coords, %index, %6
+    %6:texture_storage_2d_array<rgba8unorm, write> = load %texture
+    %7:vec4<f32> = swizzle %value, zyxw
+    %8:void = textureStore %6, %coords, %index, %7
     ret
   }
 }
diff --git a/src/tint/lang/core/ir/transform/combine_access_instructions_test.cc b/src/tint/lang/core/ir/transform/combine_access_instructions_test.cc
index 2d07019..404b94b 100644
--- a/src/tint/lang/core/ir/transform/combine_access_instructions_test.cc
+++ b/src/tint/lang/core/ir/transform/combine_access_instructions_test.cc
@@ -677,6 +677,7 @@
     auto* func = b.Function("foo", ty.f32());
     auto* indices = b.FunctionParam("indices", ty.array<u32, 4>());
     auto* values = b.FunctionParam("values", ty.array<f32, 4>());
+    func->SetParams({indices, values});
     b.Append(func->Block(), [&] {
         auto* access_index = b.Access(ty.u32(), indices, 1_u);
         auto* access_value = b.Access(ty.f32(), values, access_index);
@@ -684,11 +685,11 @@
     });
 
     auto* src = R"(
-%foo = func():f32 {
+%foo = func(%indices:array<u32, 4>, %values:array<f32, 4>):f32 {
   $B1: {
-    %2:u32 = access %indices, 1u
-    %4:f32 = access %values, %2
-    ret %4
+    %4:u32 = access %indices, 1u
+    %5:f32 = access %values, %4
+    ret %5
   }
 }
 )";
diff --git a/src/tint/lang/core/ir/transform/helper_test.h b/src/tint/lang/core/ir/transform/helper_test.h
index ef8e375..8daa4d0 100644
--- a/src/tint/lang/core/ir/transform/helper_test.h
+++ b/src/tint/lang/core/ir/transform/helper_test.h
@@ -35,7 +35,7 @@
 
 #include "gtest/gtest.h"
 #include "src/tint/lang/core/ir/builder.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/utils/containers/enum_set.h"
 
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
index d2d3263..d63e3ba 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
@@ -299,15 +299,20 @@
         if (!external_texture_params_struct) {
             external_texture_params_struct =
                 ty.Struct(sym.Register("tint_ExternalTextureParams"),
-                          {
-                              {sym.Register("numPlanes"), ty.u32()},
-                              {sym.Register("doYuvToRgbConversionOnly"), ty.u32()},
-                              {sym.Register("yuvToRgbConversionMatrix"), ty.mat3x4<f32>()},
-                              {sym.Register("gammaDecodeParams"), GammaTransferParams()},
-                              {sym.Register("gammaEncodeParams"), GammaTransferParams()},
-                              {sym.Register("gamutConversionMatrix"), ty.mat3x3<f32>()},
-                              {sym.Register("coordTransformationMatrix"), ty.mat3x2<f32>()},
-                          });
+                          {{sym.Register("numPlanes"), ty.u32()},
+                           {sym.Register("doYuvToRgbConversionOnly"), ty.u32()},
+                           {sym.Register("yuvToRgbConversionMatrix"), ty.mat3x4<f32>()},
+                           {sym.Register("gammaDecodeParams"), GammaTransferParams()},
+                           {sym.Register("gammaEncodeParams"), GammaTransferParams()},
+                           {sym.Register("gamutConversionMatrix"), ty.mat3x3<f32>()},
+                           {sym.Register("coordTransformationMatrix"), ty.mat3x2<f32>()},
+                           {sym.Register("loadTransformationMatrix"), ty.mat3x2<f32>()},
+                           {sym.Register("samplePlane0RectMin"), ty.vec2<f32>()},
+                           {sym.Register("samplePlane0RectMax"), ty.vec2<f32>()},
+                           {sym.Register("samplePlane1RectMin"), ty.vec2<f32>()},
+                           {sym.Register("samplePlane1RectMax"), ty.vec2<f32>()},
+                           {sym.Register("displayVisibleRectMax"), ty.vec2<u32>()},
+                           {sym.Register("plane1CoordFactor"), ty.vec2<f32>()}});
         }
         return external_texture_params_struct;
     }
@@ -367,32 +372,31 @@
         }
 
         // The helper function implements the following:
-        //   fn tint_TextureLoadExternal(plane0 : texture_2d<f32>,
-        //                               plane1 : texture_2d<f32>,
-        //                               coords : vec2i,
-        //                               params : ExternalTextureParams) -> vec4f {
-        //     var rgb : vec3f;
-        //     var alpha : f32;
+        // fn tint_TextureLoadExternal(plane0 : texture_2d<f32>,
+        //                             plane1 : texture_2d<f32>,
+        //                             coords : vec2<u32>,
+        //                             params : ExternalTextureParams) ->vec4f {
+        //     let clampedCoords = min(coords, params.displayVisibleRectMax);
+        //     let plane0_clamped = vec2<u32>(
+        //         round(params.loadTransformationMatrix * vec3<f32>(vec2<f32>(clampedCoords), 1)));
+        //     var color : vec4<f32>;
         //     if ((params.numPlanes == 1)) {
-        //       let texel = textureLoad(plane0, coord, 0);
-        //       rgb = texel.rgb;
-        //       alpha = texel.a;
+        //         color = textureLoad(plane0, plane0_clamped, 0).rgba;
         //     } else {
-        //       let y = textureLoad(plane0, coord, 0).r;
-        //       let coord_uv = (coord >> vec2u(1));
-        //       let uv = textureLoad(plane1, coord_uv, 0).rg;
-        //       rgb = vec4f(y, uv, 1) * params.yuvToRgbConversionMatrix;
-        //       alpha = 1.0;
-        //     }
+        //         let plane1_clamped = vec2<f32>(plane0_clamped) * params.plane1CoordFactor;
         //
-        //     if (params.doYuvToRgbConversionOnly == 0) {
-        //       rgb = gammaCorrection(rgb, params.gammaDecodeParams);
-        //       rgb = params.gamutConversionMatrix * rgb;
-        //       rgb = gammaCorrection(rgb, params.gammaEncodeParams);
+        //         color = vec4<f32>((vec4<f32>(textureLoad(plane0, plane0_clamped, 0).r,
+        //                                      textureLoad(plane1, plane1_clamped, 0).rg, 1) *
+        //                            params.yuvToRgbConversionMatrix),
+        //                           1);
         //     }
-        //
-        //     return vec4f(rgb, alpha);
-        //   }
+        //     if ((params.doYuvToRgbConversionOnly == 0)) {
+        //         color = vec4<f32>(gammaCorrection(color.rgb, params.gammaDecodeParams), color.a);
+        //         color = vec4<f32>((params.gamutConversionMatrix * color.rgb), color.a);
+        //         color = vec4<f32>(gammaCorrection(color.rgb, params.gammaEncodeParams), color.a);
+        //     }
+        //     return color;
+        // }
         texture_load_external = b.Function("tint_TextureLoadExternal", ty.vec4<f32>());
         auto* plane_0 = b.FunctionParam("plane_0", SampledTexture());
         auto* plane_1 = b.FunctionParam("plane_1", SampledTexture());
@@ -403,8 +407,21 @@
             auto* vec2f = ty.vec2<f32>();
             auto* vec3f = ty.vec3<f32>();
             auto* vec4f = ty.vec4<f32>();
+            auto* vec2u = ty.vec2<u32>();
             auto* yuv_to_rgb_conversion_only = b.Access(ty.u32(), params, 1_u);
             auto* yuv_to_rgb_conversion = b.Access(ty.mat3x4<f32>(), params, 2_u);
+            auto* load_transform_matrix = b.Access(ty.mat3x2<f32>(), params, 7_u);
+            auto* display_visible_rect_max = b.Access(ty.vec2<u32>(), params, 12_u);
+            auto* plane1_coord_factor = b.Access(ty.vec2<f32>(), params, 13_u);
+
+            auto* clamped_coords =
+                b.Call(vec2u, core::BuiltinFn::kMin, coords, display_visible_rect_max);
+            auto* clamped_coords_f = b.Convert(vec2f, clamped_coords);
+            auto* modified_coords =
+                b.Multiply(vec2f, load_transform_matrix, b.Construct(vec3f, clamped_coords_f, 1_f));
+            auto* plane0_clamped_f = b.Call(vec2f, core::BuiltinFn::kRound, modified_coords);
+
+            auto* plane0_clamped = b.Convert(vec2u, plane0_clamped_f);
 
             auto* rgb_result = b.InstructionResult(vec3f);
             auto* alpha_result = b.InstructionResult(ty.f32());
@@ -413,7 +430,8 @@
             if_planes_eq_1->SetResults(rgb_result, alpha_result);
             b.Append(if_planes_eq_1->True(), [&] {
                 // Load the texel from the first plane and split into separate rgb and a values.
-                auto* texel = b.Call(vec4f, core::BuiltinFn::kTextureLoad, plane_0, coords, 0_u);
+                auto* texel =
+                    b.Call(vec4f, core::BuiltinFn::kTextureLoad, plane_0, plane0_clamped, 0_u);
                 auto* rgb = b.Swizzle(vec3f, texel, {0u, 1u, 2u});
                 auto* a = b.Access(ty.f32(), texel, 3_u);
                 b.ExitIf(if_planes_eq_1, rgb, a);
@@ -421,14 +439,17 @@
             b.Append(if_planes_eq_1->False(), [&] {
                 // Load the y value from the first plane.
                 auto* y = b.Access(
-                    ty.f32(), b.Call(vec4f, core::BuiltinFn::kTextureLoad, plane_0, coords, 0_u),
+                    ty.f32(),
+                    b.Call(vec4f, core::BuiltinFn::kTextureLoad, plane_0, plane0_clamped, 0_u),
                     0_u);
 
                 // Load the uv value from the second plane.
-                auto* coord_uv =
-                    b.ShiftRight(ty.vec2<u32>(), coords, b.Splat(ty.vec2<u32>(), 1_u, 2u));
+                auto* plane1_clamped_f = b.Multiply(vec2f, plane0_clamped_f, plane1_coord_factor);
+
+                auto* plane1_clamped = b.Convert(vec2u, plane1_clamped_f);
                 auto* uv = b.Swizzle(
-                    vec2f, b.Call(vec4f, core::BuiltinFn::kTextureLoad, plane_1, coord_uv, 0_u),
+                    vec2f,
+                    b.Call(vec4f, core::BuiltinFn::kTextureLoad, plane_1, plane1_clamped, 0_u),
                     {0u, 1u});
 
                 // Convert the combined yuv value into rgb and set the alpha to 1.0.
@@ -468,41 +489,36 @@
         }
 
         // The helper function implements the following:
-        //   fn textureSampleExternal(plane0 : texture_2d<f32>,
-        //                            plane1 : texture_2d<f32>,
-        //                            smp : sampler,
-        //                            coord : vec2f,
-        //                            params : ExternalTextureParams) -> vec4f {
-        //     let modified_coords = params.coordTransformationMatrix * vec3f(coord, 1);
-        //     let plane0_dims = vec2f(textureDimensions(plane0));
-        //     let plane0_half_texel = vec2f(0.5) / plane0_dims;
-        //     let plane0_clamped = clamp(modified_coords, plane0_half_texel,
-        //                                (1 - plane0_half_texel));
-        //     let plane1_dims = vec2f(textureDimensions(plane1));
-        //     let plane1_half_texel = vec2f(0.5) / plane1_dims;
-        //     let plane1_clamped = clamp(modified_coords, plane1_half_texel,
-        //                          (1 - plane1_half_texel));
-        //     var rgb : vec3f;
-        //     var alpha : f32;
+        // fn textureSampleExternal(plane0 : texture_2d<f32>,
+        //                          plane1 : texture_2d<f32>,
+        //                          smp    : sampler,
+        //                          coord  : vec2f,
+        //                          params : ExternalTextureParams) ->vec4f {
+        //     let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
+        //     let plane0_clamped =
+        //         clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
+        //     var color : vec4<f32>;
+        //
         //     if ((params.numPlanes == 1)) {
-        //       let texel = textureSampleLevel(plane0, smp, plane0_clamped, 0);
-        //       rgb = texel.rgb;
-        //       alpha = texel.a;
+        //         color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
         //     } else {
-        //       let y = textureSampleLevel(plane0, smp, plane0_clamped, 0).r;
-        //       let uv = textureSampleLevel(plane1, smp, plane1_clamped, 0).rg;
-        //       rgb = vec4f(y, uv, 1.0) * params.yuvToRgbConversionMatrix;
-        //       alpha = 1.0;
+        //         let plane1_clamped =
+        //             clamp(modifiedCoords, params.samplePlane1RectMin,
+        //             params.samplePlane1RectMax);
+        //        color = vec4<f32>(
+        //                   vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r,
+        //                             textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) *
+        //                   params.yuvToRgbConversionMatrix), 1);
         //     }
         //
-        //     if (params.doYuvToRgbConversionOnly == 0) {
-        //       rgb = gammaCorrection(rgb, params.gammaDecodeParams);
-        //       rgb = params.gamutConversionMatrix * rgb;
-        //       rgb = gammaCorrection(rgb, params.gammaEncodeParams);
+        //     if ((params.doYuvToRgbConversionOnly == 0)) {
+        //         color = vec4<f32>(gammaCorrection(color.rgb, params.gammaDecodeParams), color.a);
+        //         color = vec4<f32>((params.gamutConversionMatrix * color.rgb), color.a);
+        //         color = vec4<f32>(gammaCorrection(color.rgb, params.gammaEncodeParams), color.a);
         //     }
         //
-        //     return vec4f(rgb, alpha);
-        //   }
+        //     return color;
+        // }
         texture_sample_external = b.Function("tint_TextureSampleExternal", ty.vec4<f32>());
         auto* plane_0 = b.FunctionParam("plane_0", SampledTexture());
         auto* plane_1 = b.FunctionParam("plane_1", SampledTexture());
@@ -517,21 +533,15 @@
             auto* yuv_to_rgb_conversion_only = b.Access(ty.u32(), params, 1_u);
             auto* yuv_to_rgb_conversion = b.Access(ty.mat3x4<f32>(), params, 2_u);
             auto* transformation_matrix = b.Access(ty.mat3x2<f32>(), params, 6_u);
+            auto* sample_plane0_rect_min = b.Access(ty.vec2<f32>(), params, 8_u);
+            auto* sample_plane0_rect_max = b.Access(ty.vec2<f32>(), params, 9_u);
+            auto* sample_plane1_rect_min = b.Access(ty.vec2<f32>(), params, 10_u);
+            auto* sample_plane1_rect_max = b.Access(ty.vec2<f32>(), params, 11_u);
 
             auto* modified_coords =
                 b.Multiply(vec2f, transformation_matrix, b.Construct(vec3f, coords, 1_f));
-            auto* plane0_dims = b.Convert(
-                vec2f, b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, plane_0));
-            auto* plane0_half_texel = b.Divide(vec2f, b.Splat(vec2f, 0.5_f, 2u), plane0_dims);
-            auto* plane0_clamped =
-                b.Call(vec2f, core::BuiltinFn::kClamp, modified_coords, plane0_half_texel,
-                       b.Subtract(vec2f, 1_f, plane0_half_texel));
-            auto* plane1_dims = b.Convert(
-                vec2f, b.Call(ty.vec2<u32>(), core::BuiltinFn::kTextureDimensions, plane_1));
-            auto* plane1_half_texel = b.Divide(vec2f, b.Splat(vec2f, 0.5_f, 2u), plane1_dims);
-            auto* plane1_clamped =
-                b.Call(vec2f, core::BuiltinFn::kClamp, modified_coords, plane1_half_texel,
-                       b.Subtract(vec2f, 1_f, plane1_half_texel));
+            auto* plane0_clamped = b.Call(vec2f, core::BuiltinFn::kClamp, modified_coords,
+                                          sample_plane0_rect_min, sample_plane0_rect_max);
 
             auto* rgb_result = b.InstructionResult(vec3f);
             auto* alpha_result = b.InstructionResult(ty.f32());
@@ -552,6 +562,8 @@
                                    b.Call(vec4f, core::BuiltinFn::kTextureSampleLevel, plane_0,
                                           sampler, plane0_clamped, 0_f),
                                    0_u);
+                auto* plane1_clamped = b.Call(vec2f, core::BuiltinFn::kClamp, modified_coords,
+                                              sample_plane1_rect_min, sample_plane1_rect_max);
 
                 // Sample the uv value from the second plane.
                 auto* uv = b.Swizzle(vec2f,
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc
index dadb543..6597fb8 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc
@@ -98,6 +98,13 @@
   gammaEncodeParams:tint_GammaTransferParams @offset(96)
   gamutConversionMatrix:mat3x3<f32> @offset(128)
   coordTransformationMatrix:mat3x2<f32> @offset(176)
+  loadTransformationMatrix:mat3x2<f32> @offset(200)
+  samplePlane0RectMin:vec2<f32> @offset(224)
+  samplePlane0RectMax:vec2<f32> @offset(232)
+  samplePlane1RectMin:vec2<f32> @offset(240)
+  samplePlane1RectMax:vec2<f32> @offset(248)
+  displayVisibleRectMax:vec2<u32> @offset(256)
+  plane1CoordFactor:vec2<f32> @offset(264)
 }
 
 $B1: {  # root
@@ -164,6 +171,13 @@
   gammaEncodeParams:tint_GammaTransferParams @offset(96)
   gamutConversionMatrix:mat3x3<f32> @offset(128)
   coordTransformationMatrix:mat3x2<f32> @offset(176)
+  loadTransformationMatrix:mat3x2<f32> @offset(200)
+  samplePlane0RectMin:vec2<f32> @offset(224)
+  samplePlane0RectMax:vec2<f32> @offset(232)
+  samplePlane1RectMin:vec2<f32> @offset(240)
+  samplePlane1RectMax:vec2<f32> @offset(248)
+  displayVisibleRectMax:vec2<u32> @offset(256)
+  plane1CoordFactor:vec2<f32> @offset(264)
 }
 
 $B1: {  # root
@@ -236,6 +250,13 @@
   gammaEncodeParams:tint_GammaTransferParams @offset(96)
   gamutConversionMatrix:mat3x3<f32> @offset(128)
   coordTransformationMatrix:mat3x2<f32> @offset(176)
+  loadTransformationMatrix:mat3x2<f32> @offset(200)
+  samplePlane0RectMin:vec2<f32> @offset(224)
+  samplePlane0RectMax:vec2<f32> @offset(232)
+  samplePlane1RectMin:vec2<f32> @offset(240)
+  samplePlane1RectMax:vec2<f32> @offset(248)
+  displayVisibleRectMax:vec2<u32> @offset(256)
+  plane1CoordFactor:vec2<f32> @offset(264)
 }
 
 $B1: {  # root
@@ -311,6 +332,13 @@
   gammaEncodeParams:tint_GammaTransferParams @offset(96)
   gamutConversionMatrix:mat3x3<f32> @offset(128)
   coordTransformationMatrix:mat3x2<f32> @offset(176)
+  loadTransformationMatrix:mat3x2<f32> @offset(200)
+  samplePlane0RectMin:vec2<f32> @offset(224)
+  samplePlane0RectMax:vec2<f32> @offset(232)
+  samplePlane1RectMin:vec2<f32> @offset(240)
+  samplePlane1RectMax:vec2<f32> @offset(248)
+  displayVisibleRectMax:vec2<u32> @offset(256)
+  plane1CoordFactor:vec2<f32> @offset(264)
 }
 
 $B1: {  # root
@@ -332,69 +360,79 @@
   $B3: {
     %15:u32 = access %params, 1u
     %16:mat3x4<f32> = access %params, 2u
-    %17:u32 = access %params, 0u
-    %18:bool = eq %17, 1u
-    %19:vec3<f32>, %20:f32 = if %18 [t: $B4, f: $B5] {  # if_1
+    %17:mat3x2<f32> = access %params, 7u
+    %18:vec2<u32> = access %params, 12u
+    %19:vec2<f32> = access %params, 13u
+    %20:vec2<u32> = min %coords_1, %18
+    %21:vec2<f32> = convert %20
+    %22:vec3<f32> = construct %21, 1.0f
+    %23:vec2<f32> = mul %17, %22
+    %24:vec2<f32> = round %23
+    %25:vec2<u32> = convert %24
+    %26:u32 = access %params, 0u
+    %27:bool = eq %26, 1u
+    %28:vec3<f32>, %29:f32 = if %27 [t: $B4, f: $B5] {  # if_1
       $B4: {  # true
-        %21:vec4<f32> = textureLoad %plane_0, %coords_1, 0u
-        %22:vec3<f32> = swizzle %21, xyz
-        %23:f32 = access %21, 3u
-        exit_if %22, %23  # if_1
+        %30:vec4<f32> = textureLoad %plane_0, %25, 0u
+        %31:vec3<f32> = swizzle %30, xyz
+        %32:f32 = access %30, 3u
+        exit_if %31, %32  # if_1
       }
       $B5: {  # false
-        %24:vec4<f32> = textureLoad %plane_0, %coords_1, 0u
-        %25:f32 = access %24, 0u
-        %26:vec2<u32> = shr %coords_1, vec2<u32>(1u)
-        %27:vec4<f32> = textureLoad %plane_1, %26, 0u
-        %28:vec2<f32> = swizzle %27, xy
-        %29:vec4<f32> = construct %25, %28, 1.0f
-        %30:vec3<f32> = mul %29, %16
-        exit_if %30, 1.0f  # if_1
+        %33:vec4<f32> = textureLoad %plane_0, %25, 0u
+        %34:f32 = access %33, 0u
+        %35:vec2<f32> = mul %24, %19
+        %36:vec2<u32> = convert %35
+        %37:vec4<f32> = textureLoad %plane_1, %36, 0u
+        %38:vec2<f32> = swizzle %37, xy
+        %39:vec4<f32> = construct %34, %38, 1.0f
+        %40:vec3<f32> = mul %39, %16
+        exit_if %40, 1.0f  # if_1
       }
     }
-    %31:bool = eq %15, 0u
-    %32:vec3<f32> = if %31 [t: $B6, f: $B7] {  # if_2
+    %41:bool = eq %15, 0u
+    %42:vec3<f32> = if %41 [t: $B6, f: $B7] {  # if_2
       $B6: {  # true
-        %33:tint_GammaTransferParams = access %params, 3u
-        %34:tint_GammaTransferParams = access %params, 4u
-        %35:mat3x3<f32> = access %params, 5u
-        %36:vec3<f32> = call %tint_GammaCorrection, %19, %33
-        %38:vec3<f32> = mul %35, %36
-        %39:vec3<f32> = call %tint_GammaCorrection, %38, %34
-        exit_if %39  # if_2
+        %43:tint_GammaTransferParams = access %params, 3u
+        %44:tint_GammaTransferParams = access %params, 4u
+        %45:mat3x3<f32> = access %params, 5u
+        %46:vec3<f32> = call %tint_GammaCorrection, %28, %43
+        %48:vec3<f32> = mul %45, %46
+        %49:vec3<f32> = call %tint_GammaCorrection, %48, %44
+        exit_if %49  # if_2
       }
       $B7: {  # false
-        exit_if %19  # if_2
+        exit_if %28  # if_2
       }
     }
-    %40:vec4<f32> = construct %32, %20
-    ret %40
+    %50:vec4<f32> = construct %42, %29
+    ret %50
   }
 }
 %tint_GammaCorrection = func(%v:vec3<f32>, %params_1:tint_GammaTransferParams):vec3<f32> {  # %params_1: 'params'
   $B8: {
-    %43:f32 = access %params_1, 0u
-    %44:f32 = access %params_1, 1u
-    %45:f32 = access %params_1, 2u
-    %46:f32 = access %params_1, 3u
-    %47:f32 = access %params_1, 4u
-    %48:f32 = access %params_1, 5u
-    %49:f32 = access %params_1, 6u
-    %50:vec3<f32> = construct %43
-    %51:vec3<f32> = construct %47
-    %52:vec3<f32> = abs %v
-    %53:vec3<f32> = sign %v
-    %54:vec3<bool> = lt %52, %51
-    %55:vec3<f32> = mul %46, %52
-    %56:vec3<f32> = add %55, %49
-    %57:vec3<f32> = mul %53, %56
-    %58:vec3<f32> = mul %44, %52
-    %59:vec3<f32> = add %58, %45
-    %60:vec3<f32> = pow %59, %50
-    %61:vec3<f32> = add %60, %48
-    %62:vec3<f32> = mul %53, %61
-    %63:vec3<f32> = select %62, %57, %54
-    ret %63
+    %53:f32 = access %params_1, 0u
+    %54:f32 = access %params_1, 1u
+    %55:f32 = access %params_1, 2u
+    %56:f32 = access %params_1, 3u
+    %57:f32 = access %params_1, 4u
+    %58:f32 = access %params_1, 5u
+    %59:f32 = access %params_1, 6u
+    %60:vec3<f32> = construct %53
+    %61:vec3<f32> = construct %57
+    %62:vec3<f32> = abs %v
+    %63:vec3<f32> = sign %v
+    %64:vec3<bool> = lt %62, %61
+    %65:vec3<f32> = mul %56, %62
+    %66:vec3<f32> = add %65, %59
+    %67:vec3<f32> = mul %63, %66
+    %68:vec3<f32> = mul %54, %62
+    %69:vec3<f32> = add %68, %55
+    %70:vec3<f32> = pow %69, %60
+    %71:vec3<f32> = add %70, %58
+    %72:vec3<f32> = mul %63, %71
+    %73:vec3<f32> = select %72, %67, %64
+    ret %73
   }
 }
 )";
@@ -455,6 +493,13 @@
   gammaEncodeParams:tint_GammaTransferParams @offset(96)
   gamutConversionMatrix:mat3x3<f32> @offset(128)
   coordTransformationMatrix:mat3x2<f32> @offset(176)
+  loadTransformationMatrix:mat3x2<f32> @offset(200)
+  samplePlane0RectMin:vec2<f32> @offset(224)
+  samplePlane0RectMax:vec2<f32> @offset(232)
+  samplePlane1RectMin:vec2<f32> @offset(240)
+  samplePlane1RectMax:vec2<f32> @offset(248)
+  displayVisibleRectMax:vec2<u32> @offset(256)
+  plane1CoordFactor:vec2<f32> @offset(264)
 }
 
 $B1: {  # root
@@ -477,69 +522,79 @@
   $B3: {
     %16:u32 = access %params, 1u
     %17:mat3x4<f32> = access %params, 2u
-    %18:u32 = access %params, 0u
-    %19:bool = eq %18, 1u
-    %20:vec3<f32>, %21:f32 = if %19 [t: $B4, f: $B5] {  # if_1
+    %18:mat3x2<f32> = access %params, 7u
+    %19:vec2<u32> = access %params, 12u
+    %20:vec2<f32> = access %params, 13u
+    %21:vec2<u32> = min %coords_1, %19
+    %22:vec2<f32> = convert %21
+    %23:vec3<f32> = construct %22, 1.0f
+    %24:vec2<f32> = mul %18, %23
+    %25:vec2<f32> = round %24
+    %26:vec2<u32> = convert %25
+    %27:u32 = access %params, 0u
+    %28:bool = eq %27, 1u
+    %29:vec3<f32>, %30:f32 = if %28 [t: $B4, f: $B5] {  # if_1
       $B4: {  # true
-        %22:vec4<f32> = textureLoad %plane_0, %coords_1, 0u
-        %23:vec3<f32> = swizzle %22, xyz
-        %24:f32 = access %22, 3u
-        exit_if %23, %24  # if_1
+        %31:vec4<f32> = textureLoad %plane_0, %26, 0u
+        %32:vec3<f32> = swizzle %31, xyz
+        %33:f32 = access %31, 3u
+        exit_if %32, %33  # if_1
       }
       $B5: {  # false
-        %25:vec4<f32> = textureLoad %plane_0, %coords_1, 0u
-        %26:f32 = access %25, 0u
-        %27:vec2<u32> = shr %coords_1, vec2<u32>(1u)
-        %28:vec4<f32> = textureLoad %plane_1, %27, 0u
-        %29:vec2<f32> = swizzle %28, xy
-        %30:vec4<f32> = construct %26, %29, 1.0f
-        %31:vec3<f32> = mul %30, %17
-        exit_if %31, 1.0f  # if_1
+        %34:vec4<f32> = textureLoad %plane_0, %26, 0u
+        %35:f32 = access %34, 0u
+        %36:vec2<f32> = mul %25, %20
+        %37:vec2<u32> = convert %36
+        %38:vec4<f32> = textureLoad %plane_1, %37, 0u
+        %39:vec2<f32> = swizzle %38, xy
+        %40:vec4<f32> = construct %35, %39, 1.0f
+        %41:vec3<f32> = mul %40, %17
+        exit_if %41, 1.0f  # if_1
       }
     }
-    %32:bool = eq %16, 0u
-    %33:vec3<f32> = if %32 [t: $B6, f: $B7] {  # if_2
+    %42:bool = eq %16, 0u
+    %43:vec3<f32> = if %42 [t: $B6, f: $B7] {  # if_2
       $B6: {  # true
-        %34:tint_GammaTransferParams = access %params, 3u
-        %35:tint_GammaTransferParams = access %params, 4u
-        %36:mat3x3<f32> = access %params, 5u
-        %37:vec3<f32> = call %tint_GammaCorrection, %20, %34
-        %39:vec3<f32> = mul %36, %37
-        %40:vec3<f32> = call %tint_GammaCorrection, %39, %35
-        exit_if %40  # if_2
+        %44:tint_GammaTransferParams = access %params, 3u
+        %45:tint_GammaTransferParams = access %params, 4u
+        %46:mat3x3<f32> = access %params, 5u
+        %47:vec3<f32> = call %tint_GammaCorrection, %29, %44
+        %49:vec3<f32> = mul %46, %47
+        %50:vec3<f32> = call %tint_GammaCorrection, %49, %45
+        exit_if %50  # if_2
       }
       $B7: {  # false
-        exit_if %20  # if_2
+        exit_if %29  # if_2
       }
     }
-    %41:vec4<f32> = construct %33, %21
-    ret %41
+    %51:vec4<f32> = construct %43, %30
+    ret %51
   }
 }
 %tint_GammaCorrection = func(%v:vec3<f32>, %params_1:tint_GammaTransferParams):vec3<f32> {  # %params_1: 'params'
   $B8: {
-    %44:f32 = access %params_1, 0u
-    %45:f32 = access %params_1, 1u
-    %46:f32 = access %params_1, 2u
-    %47:f32 = access %params_1, 3u
-    %48:f32 = access %params_1, 4u
-    %49:f32 = access %params_1, 5u
-    %50:f32 = access %params_1, 6u
-    %51:vec3<f32> = construct %44
-    %52:vec3<f32> = construct %48
-    %53:vec3<f32> = abs %v
-    %54:vec3<f32> = sign %v
-    %55:vec3<bool> = lt %53, %52
-    %56:vec3<f32> = mul %47, %53
-    %57:vec3<f32> = add %56, %50
-    %58:vec3<f32> = mul %54, %57
-    %59:vec3<f32> = mul %45, %53
-    %60:vec3<f32> = add %59, %46
-    %61:vec3<f32> = pow %60, %51
-    %62:vec3<f32> = add %61, %49
-    %63:vec3<f32> = mul %54, %62
-    %64:vec3<f32> = select %63, %58, %55
-    ret %64
+    %54:f32 = access %params_1, 0u
+    %55:f32 = access %params_1, 1u
+    %56:f32 = access %params_1, 2u
+    %57:f32 = access %params_1, 3u
+    %58:f32 = access %params_1, 4u
+    %59:f32 = access %params_1, 5u
+    %60:f32 = access %params_1, 6u
+    %61:vec3<f32> = construct %54
+    %62:vec3<f32> = construct %58
+    %63:vec3<f32> = abs %v
+    %64:vec3<f32> = sign %v
+    %65:vec3<bool> = lt %63, %62
+    %66:vec3<f32> = mul %57, %63
+    %67:vec3<f32> = add %66, %60
+    %68:vec3<f32> = mul %64, %67
+    %69:vec3<f32> = mul %55, %63
+    %70:vec3<f32> = add %69, %56
+    %71:vec3<f32> = pow %70, %61
+    %72:vec3<f32> = add %71, %59
+    %73:vec3<f32> = mul %64, %72
+    %74:vec3<f32> = select %73, %68, %65
+    ret %74
   }
 }
 )";
@@ -602,6 +657,13 @@
   gammaEncodeParams:tint_GammaTransferParams @offset(96)
   gamutConversionMatrix:mat3x3<f32> @offset(128)
   coordTransformationMatrix:mat3x2<f32> @offset(176)
+  loadTransformationMatrix:mat3x2<f32> @offset(200)
+  samplePlane0RectMin:vec2<f32> @offset(224)
+  samplePlane0RectMax:vec2<f32> @offset(232)
+  samplePlane1RectMin:vec2<f32> @offset(240)
+  samplePlane1RectMax:vec2<f32> @offset(248)
+  displayVisibleRectMax:vec2<u32> @offset(256)
+  plane1CoordFactor:vec2<f32> @offset(264)
 }
 
 $B1: {  # root
@@ -624,80 +686,76 @@
     %17:u32 = access %params, 1u
     %18:mat3x4<f32> = access %params, 2u
     %19:mat3x2<f32> = access %params, 6u
-    %20:vec3<f32> = construct %coords_1, 1.0f
-    %21:vec2<f32> = mul %19, %20
-    %22:vec2<u32> = textureDimensions %plane_0
-    %23:vec2<f32> = convert %22
-    %24:vec2<f32> = div vec2<f32>(0.5f), %23
-    %25:vec2<f32> = sub 1.0f, %24
-    %26:vec2<f32> = clamp %21, %24, %25
-    %27:vec2<u32> = textureDimensions %plane_1
-    %28:vec2<f32> = convert %27
-    %29:vec2<f32> = div vec2<f32>(0.5f), %28
-    %30:vec2<f32> = sub 1.0f, %29
-    %31:vec2<f32> = clamp %21, %29, %30
-    %32:u32 = access %params, 0u
-    %33:bool = eq %32, 1u
-    %34:vec3<f32>, %35:f32 = if %33 [t: $B4, f: $B5] {  # if_1
+    %20:vec2<f32> = access %params, 8u
+    %21:vec2<f32> = access %params, 9u
+    %22:vec2<f32> = access %params, 10u
+    %23:vec2<f32> = access %params, 11u
+    %24:vec3<f32> = construct %coords_1, 1.0f
+    %25:vec2<f32> = mul %19, %24
+    %26:vec2<f32> = clamp %25, %20, %21
+    %27:u32 = access %params, 0u
+    %28:bool = eq %27, 1u
+    %29:vec3<f32>, %30:f32 = if %28 [t: $B4, f: $B5] {  # if_1
       $B4: {  # true
-        %36:vec4<f32> = textureSampleLevel %plane_0, %sampler_1, %26, 0.0f
-        %37:vec3<f32> = swizzle %36, xyz
-        %38:f32 = access %36, 3u
-        exit_if %37, %38  # if_1
+        %31:vec4<f32> = textureSampleLevel %plane_0, %sampler_1, %26, 0.0f
+        %32:vec3<f32> = swizzle %31, xyz
+        %33:f32 = access %31, 3u
+        exit_if %32, %33  # if_1
       }
       $B5: {  # false
-        %39:vec4<f32> = textureSampleLevel %plane_0, %sampler_1, %26, 0.0f
-        %40:f32 = access %39, 0u
-        %41:vec4<f32> = textureSampleLevel %plane_1, %sampler_1, %31, 0.0f
-        %42:vec2<f32> = swizzle %41, xy
-        %43:vec4<f32> = construct %40, %42, 1.0f
-        %44:vec3<f32> = mul %43, %18
-        exit_if %44, 1.0f  # if_1
+        %34:vec4<f32> = textureSampleLevel %plane_0, %sampler_1, %26, 0.0f
+        %35:f32 = access %34, 0u
+        %36:vec2<f32> = clamp %25, %22, %23
+        %37:vec4<f32> = textureSampleLevel %plane_1, %sampler_1, %36, 0.0f
+        %38:vec2<f32> = swizzle %37, xy
+        %39:vec4<f32> = construct %35, %38, 1.0f
+        %40:vec3<f32> = mul %39, %18
+        exit_if %40, 1.0f  # if_1
       }
     }
-    %45:bool = eq %17, 0u
-    %46:vec3<f32> = if %45 [t: $B6, f: $B7] {  # if_2
+    %41:bool = eq %17, 0u
+    %42:vec3<f32> = if %41 [t: $B6, f: $B7] {  # if_2
       $B6: {  # true
-        %47:tint_GammaTransferParams = access %params, 3u
-        %48:tint_GammaTransferParams = access %params, 4u
-        %49:mat3x3<f32> = access %params, 5u
-        %50:vec3<f32> = call %tint_GammaCorrection, %34, %47
-        %52:vec3<f32> = mul %49, %50
-        %53:vec3<f32> = call %tint_GammaCorrection, %52, %48
-        exit_if %53  # if_2
+        %43:tint_GammaTransferParams = access %params, 3u
+        %44:tint_GammaTransferParams = access %params, 4u
+        %45:mat3x3<f32> = access %params, 5u
+        %46:vec3<f32> = call %tint_GammaCorrection, %29, %43
+        %48:vec3<f32> = mul %45, %46
+        %49:vec3<f32> = call %tint_GammaCorrection, %48, %44
+        exit_if %49  # if_2
       }
       $B7: {  # false
-        exit_if %34  # if_2
+        exit_if %29  # if_2
       }
     }
-    %54:vec4<f32> = construct %46, %35
-    ret %54
+    %50:vec4<f32> = construct %42, %30
+    ret %50
   }
 }
 %tint_GammaCorrection = func(%v:vec3<f32>, %params_1:tint_GammaTransferParams):vec3<f32> {  # %params_1: 'params'
   $B8: {
-    %57:f32 = access %params_1, 0u
-    %58:f32 = access %params_1, 1u
-    %59:f32 = access %params_1, 2u
-    %60:f32 = access %params_1, 3u
-    %61:f32 = access %params_1, 4u
-    %62:f32 = access %params_1, 5u
-    %63:f32 = access %params_1, 6u
-    %64:vec3<f32> = construct %57
-    %65:vec3<f32> = construct %61
-    %66:vec3<f32> = abs %v
-    %67:vec3<f32> = sign %v
-    %68:vec3<bool> = lt %66, %65
-    %69:vec3<f32> = mul %60, %66
-    %70:vec3<f32> = add %69, %63
-    %71:vec3<f32> = mul %67, %70
-    %72:vec3<f32> = mul %58, %66
-    %73:vec3<f32> = add %72, %59
-    %74:vec3<f32> = pow %73, %64
-    %75:vec3<f32> = add %74, %62
-    %76:vec3<f32> = mul %67, %75
-    %77:vec3<f32> = select %76, %71, %68
-    ret %77
+    %53:f32 = access %params_1, 0u
+    %54:f32 = access %params_1, 1u
+    %55:f32 = access %params_1, 2u
+    %56:f32 = access %params_1, 3u
+    %57:f32 = access %params_1, 4u
+    %58:f32 = access %params_1, 5u
+    %59:f32 = access %params_1, 6u
+    %60:vec3<f32> = construct %53
+    %61:vec3<f32> = construct %57
+    %62:vec3<f32> = abs %v
+    %63:vec3<f32> = sign %v
+    %64:vec3<bool> = lt %62, %61
+    %65:vec3<f32> = mul %56, %62
+    %66:vec3<f32> = add %65, %59
+    %67:vec3<f32> = mul %63, %66
+    %68:vec3<f32> = mul %54, %62
+    %69:vec3<f32> = add %68, %55
+    %70:vec3<f32> = pow %69, %60
+    %71:vec3<f32> = add %70, %58
+    %72:vec3<f32> = mul %63, %71
+    %73:vec3<f32> = select %72, %67, %64
+    ret %73
   }
 }
 )";
@@ -781,6 +839,13 @@
   gammaEncodeParams:tint_GammaTransferParams @offset(96)
   gamutConversionMatrix:mat3x3<f32> @offset(128)
   coordTransformationMatrix:mat3x2<f32> @offset(176)
+  loadTransformationMatrix:mat3x2<f32> @offset(200)
+  samplePlane0RectMin:vec2<f32> @offset(224)
+  samplePlane0RectMax:vec2<f32> @offset(232)
+  samplePlane1RectMin:vec2<f32> @offset(240)
+  samplePlane1RectMax:vec2<f32> @offset(248)
+  displayVisibleRectMax:vec2<u32> @offset(256)
+  plane1CoordFactor:vec2<f32> @offset(264)
 }
 
 $B1: {  # root
@@ -809,80 +874,76 @@
     %24:u32 = access %params, 1u
     %25:mat3x4<f32> = access %params, 2u
     %26:mat3x2<f32> = access %params, 6u
-    %27:vec3<f32> = construct %coords_2, 1.0f
-    %28:vec2<f32> = mul %26, %27
-    %29:vec2<u32> = textureDimensions %plane_0
-    %30:vec2<f32> = convert %29
-    %31:vec2<f32> = div vec2<f32>(0.5f), %30
-    %32:vec2<f32> = sub 1.0f, %31
-    %33:vec2<f32> = clamp %28, %31, %32
-    %34:vec2<u32> = textureDimensions %plane_1
-    %35:vec2<f32> = convert %34
-    %36:vec2<f32> = div vec2<f32>(0.5f), %35
-    %37:vec2<f32> = sub 1.0f, %36
-    %38:vec2<f32> = clamp %28, %36, %37
-    %39:u32 = access %params, 0u
-    %40:bool = eq %39, 1u
-    %41:vec3<f32>, %42:f32 = if %40 [t: $B5, f: $B6] {  # if_1
+    %27:vec2<f32> = access %params, 8u
+    %28:vec2<f32> = access %params, 9u
+    %29:vec2<f32> = access %params, 10u
+    %30:vec2<f32> = access %params, 11u
+    %31:vec3<f32> = construct %coords_2, 1.0f
+    %32:vec2<f32> = mul %26, %31
+    %33:vec2<f32> = clamp %32, %27, %28
+    %34:u32 = access %params, 0u
+    %35:bool = eq %34, 1u
+    %36:vec3<f32>, %37:f32 = if %35 [t: $B5, f: $B6] {  # if_1
       $B5: {  # true
-        %43:vec4<f32> = textureSampleLevel %plane_0, %sampler_2, %33, 0.0f
-        %44:vec3<f32> = swizzle %43, xyz
-        %45:f32 = access %43, 3u
-        exit_if %44, %45  # if_1
+        %38:vec4<f32> = textureSampleLevel %plane_0, %sampler_2, %33, 0.0f
+        %39:vec3<f32> = swizzle %38, xyz
+        %40:f32 = access %38, 3u
+        exit_if %39, %40  # if_1
       }
       $B6: {  # false
-        %46:vec4<f32> = textureSampleLevel %plane_0, %sampler_2, %33, 0.0f
-        %47:f32 = access %46, 0u
-        %48:vec4<f32> = textureSampleLevel %plane_1, %sampler_2, %38, 0.0f
-        %49:vec2<f32> = swizzle %48, xy
-        %50:vec4<f32> = construct %47, %49, 1.0f
-        %51:vec3<f32> = mul %50, %25
-        exit_if %51, 1.0f  # if_1
+        %41:vec4<f32> = textureSampleLevel %plane_0, %sampler_2, %33, 0.0f
+        %42:f32 = access %41, 0u
+        %43:vec2<f32> = clamp %32, %29, %30
+        %44:vec4<f32> = textureSampleLevel %plane_1, %sampler_2, %43, 0.0f
+        %45:vec2<f32> = swizzle %44, xy
+        %46:vec4<f32> = construct %42, %45, 1.0f
+        %47:vec3<f32> = mul %46, %25
+        exit_if %47, 1.0f  # if_1
       }
     }
-    %52:bool = eq %24, 0u
-    %53:vec3<f32> = if %52 [t: $B7, f: $B8] {  # if_2
+    %48:bool = eq %24, 0u
+    %49:vec3<f32> = if %48 [t: $B7, f: $B8] {  # if_2
       $B7: {  # true
-        %54:tint_GammaTransferParams = access %params, 3u
-        %55:tint_GammaTransferParams = access %params, 4u
-        %56:mat3x3<f32> = access %params, 5u
-        %57:vec3<f32> = call %tint_GammaCorrection, %41, %54
-        %59:vec3<f32> = mul %56, %57
-        %60:vec3<f32> = call %tint_GammaCorrection, %59, %55
-        exit_if %60  # if_2
+        %50:tint_GammaTransferParams = access %params, 3u
+        %51:tint_GammaTransferParams = access %params, 4u
+        %52:mat3x3<f32> = access %params, 5u
+        %53:vec3<f32> = call %tint_GammaCorrection, %36, %50
+        %55:vec3<f32> = mul %52, %53
+        %56:vec3<f32> = call %tint_GammaCorrection, %55, %51
+        exit_if %56  # if_2
       }
       $B8: {  # false
-        exit_if %41  # if_2
+        exit_if %36  # if_2
       }
     }
-    %61:vec4<f32> = construct %53, %42
-    ret %61
+    %57:vec4<f32> = construct %49, %37
+    ret %57
   }
 }
 %tint_GammaCorrection = func(%v:vec3<f32>, %params_1:tint_GammaTransferParams):vec3<f32> {  # %params_1: 'params'
   $B9: {
-    %64:f32 = access %params_1, 0u
-    %65:f32 = access %params_1, 1u
-    %66:f32 = access %params_1, 2u
-    %67:f32 = access %params_1, 3u
-    %68:f32 = access %params_1, 4u
-    %69:f32 = access %params_1, 5u
-    %70:f32 = access %params_1, 6u
-    %71:vec3<f32> = construct %64
-    %72:vec3<f32> = construct %68
-    %73:vec3<f32> = abs %v
-    %74:vec3<f32> = sign %v
-    %75:vec3<bool> = lt %73, %72
-    %76:vec3<f32> = mul %67, %73
-    %77:vec3<f32> = add %76, %70
-    %78:vec3<f32> = mul %74, %77
-    %79:vec3<f32> = mul %65, %73
-    %80:vec3<f32> = add %79, %66
-    %81:vec3<f32> = pow %80, %71
-    %82:vec3<f32> = add %81, %69
-    %83:vec3<f32> = mul %74, %82
-    %84:vec3<f32> = select %83, %78, %75
-    ret %84
+    %60:f32 = access %params_1, 0u
+    %61:f32 = access %params_1, 1u
+    %62:f32 = access %params_1, 2u
+    %63:f32 = access %params_1, 3u
+    %64:f32 = access %params_1, 4u
+    %65:f32 = access %params_1, 5u
+    %66:f32 = access %params_1, 6u
+    %67:vec3<f32> = construct %60
+    %68:vec3<f32> = construct %64
+    %69:vec3<f32> = abs %v
+    %70:vec3<f32> = sign %v
+    %71:vec3<bool> = lt %69, %68
+    %72:vec3<f32> = mul %63, %69
+    %73:vec3<f32> = add %72, %66
+    %74:vec3<f32> = mul %70, %73
+    %75:vec3<f32> = mul %61, %69
+    %76:vec3<f32> = add %75, %62
+    %77:vec3<f32> = pow %76, %67
+    %78:vec3<f32> = add %77, %65
+    %79:vec3<f32> = mul %70, %78
+    %80:vec3<f32> = select %79, %74, %71
+    ret %80
   }
 }
 )";
@@ -984,6 +1045,13 @@
   gammaEncodeParams:tint_GammaTransferParams @offset(96)
   gamutConversionMatrix:mat3x3<f32> @offset(128)
   coordTransformationMatrix:mat3x2<f32> @offset(176)
+  loadTransformationMatrix:mat3x2<f32> @offset(200)
+  samplePlane0RectMin:vec2<f32> @offset(224)
+  samplePlane0RectMax:vec2<f32> @offset(232)
+  samplePlane1RectMin:vec2<f32> @offset(240)
+  samplePlane1RectMax:vec2<f32> @offset(248)
+  displayVisibleRectMax:vec2<u32> @offset(256)
+  plane1CoordFactor:vec2<f32> @offset(264)
 }
 
 $B1: {  # root
@@ -1026,80 +1094,76 @@
     %38:u32 = access %params, 1u
     %39:mat3x4<f32> = access %params, 2u
     %40:mat3x2<f32> = access %params, 6u
-    %41:vec3<f32> = construct %coords_2, 1.0f
-    %42:vec2<f32> = mul %40, %41
-    %43:vec2<u32> = textureDimensions %plane_0
-    %44:vec2<f32> = convert %43
-    %45:vec2<f32> = div vec2<f32>(0.5f), %44
-    %46:vec2<f32> = sub 1.0f, %45
-    %47:vec2<f32> = clamp %42, %45, %46
-    %48:vec2<u32> = textureDimensions %plane_1
-    %49:vec2<f32> = convert %48
-    %50:vec2<f32> = div vec2<f32>(0.5f), %49
-    %51:vec2<f32> = sub 1.0f, %50
-    %52:vec2<f32> = clamp %42, %50, %51
-    %53:u32 = access %params, 0u
-    %54:bool = eq %53, 1u
-    %55:vec3<f32>, %56:f32 = if %54 [t: $B5, f: $B6] {  # if_1
+    %41:vec2<f32> = access %params, 8u
+    %42:vec2<f32> = access %params, 9u
+    %43:vec2<f32> = access %params, 10u
+    %44:vec2<f32> = access %params, 11u
+    %45:vec3<f32> = construct %coords_2, 1.0f
+    %46:vec2<f32> = mul %40, %45
+    %47:vec2<f32> = clamp %46, %41, %42
+    %48:u32 = access %params, 0u
+    %49:bool = eq %48, 1u
+    %50:vec3<f32>, %51:f32 = if %49 [t: $B5, f: $B6] {  # if_1
       $B5: {  # true
-        %57:vec4<f32> = textureSampleLevel %plane_0, %sampler_2, %47, 0.0f
-        %58:vec3<f32> = swizzle %57, xyz
-        %59:f32 = access %57, 3u
-        exit_if %58, %59  # if_1
+        %52:vec4<f32> = textureSampleLevel %plane_0, %sampler_2, %47, 0.0f
+        %53:vec3<f32> = swizzle %52, xyz
+        %54:f32 = access %52, 3u
+        exit_if %53, %54  # if_1
       }
       $B6: {  # false
-        %60:vec4<f32> = textureSampleLevel %plane_0, %sampler_2, %47, 0.0f
-        %61:f32 = access %60, 0u
-        %62:vec4<f32> = textureSampleLevel %plane_1, %sampler_2, %52, 0.0f
-        %63:vec2<f32> = swizzle %62, xy
-        %64:vec4<f32> = construct %61, %63, 1.0f
-        %65:vec3<f32> = mul %64, %39
-        exit_if %65, 1.0f  # if_1
+        %55:vec4<f32> = textureSampleLevel %plane_0, %sampler_2, %47, 0.0f
+        %56:f32 = access %55, 0u
+        %57:vec2<f32> = clamp %46, %43, %44
+        %58:vec4<f32> = textureSampleLevel %plane_1, %sampler_2, %57, 0.0f
+        %59:vec2<f32> = swizzle %58, xy
+        %60:vec4<f32> = construct %56, %59, 1.0f
+        %61:vec3<f32> = mul %60, %39
+        exit_if %61, 1.0f  # if_1
       }
     }
-    %66:bool = eq %38, 0u
-    %67:vec3<f32> = if %66 [t: $B7, f: $B8] {  # if_2
+    %62:bool = eq %38, 0u
+    %63:vec3<f32> = if %62 [t: $B7, f: $B8] {  # if_2
       $B7: {  # true
-        %68:tint_GammaTransferParams = access %params, 3u
-        %69:tint_GammaTransferParams = access %params, 4u
-        %70:mat3x3<f32> = access %params, 5u
-        %71:vec3<f32> = call %tint_GammaCorrection, %55, %68
-        %73:vec3<f32> = mul %70, %71
-        %74:vec3<f32> = call %tint_GammaCorrection, %73, %69
-        exit_if %74  # if_2
+        %64:tint_GammaTransferParams = access %params, 3u
+        %65:tint_GammaTransferParams = access %params, 4u
+        %66:mat3x3<f32> = access %params, 5u
+        %67:vec3<f32> = call %tint_GammaCorrection, %50, %64
+        %69:vec3<f32> = mul %66, %67
+        %70:vec3<f32> = call %tint_GammaCorrection, %69, %65
+        exit_if %70  # if_2
       }
       $B8: {  # false
-        exit_if %55  # if_2
+        exit_if %50  # if_2
       }
     }
-    %75:vec4<f32> = construct %67, %56
-    ret %75
+    %71:vec4<f32> = construct %63, %51
+    ret %71
   }
 }
 %tint_GammaCorrection = func(%v:vec3<f32>, %params_1:tint_GammaTransferParams):vec3<f32> {  # %params_1: 'params'
   $B9: {
-    %78:f32 = access %params_1, 0u
-    %79:f32 = access %params_1, 1u
-    %80:f32 = access %params_1, 2u
-    %81:f32 = access %params_1, 3u
-    %82:f32 = access %params_1, 4u
-    %83:f32 = access %params_1, 5u
-    %84:f32 = access %params_1, 6u
-    %85:vec3<f32> = construct %78
-    %86:vec3<f32> = construct %82
-    %87:vec3<f32> = abs %v
-    %88:vec3<f32> = sign %v
-    %89:vec3<bool> = lt %87, %86
-    %90:vec3<f32> = mul %81, %87
-    %91:vec3<f32> = add %90, %84
-    %92:vec3<f32> = mul %88, %91
-    %93:vec3<f32> = mul %79, %87
-    %94:vec3<f32> = add %93, %80
-    %95:vec3<f32> = pow %94, %85
-    %96:vec3<f32> = add %95, %83
-    %97:vec3<f32> = mul %88, %96
-    %98:vec3<f32> = select %97, %92, %89
-    ret %98
+    %74:f32 = access %params_1, 0u
+    %75:f32 = access %params_1, 1u
+    %76:f32 = access %params_1, 2u
+    %77:f32 = access %params_1, 3u
+    %78:f32 = access %params_1, 4u
+    %79:f32 = access %params_1, 5u
+    %80:f32 = access %params_1, 6u
+    %81:vec3<f32> = construct %74
+    %82:vec3<f32> = construct %78
+    %83:vec3<f32> = abs %v
+    %84:vec3<f32> = sign %v
+    %85:vec3<bool> = lt %83, %82
+    %86:vec3<f32> = mul %77, %83
+    %87:vec3<f32> = add %86, %80
+    %88:vec3<f32> = mul %84, %87
+    %89:vec3<f32> = mul %75, %83
+    %90:vec3<f32> = add %89, %76
+    %91:vec3<f32> = pow %90, %81
+    %92:vec3<f32> = add %91, %79
+    %93:vec3<f32> = mul %84, %92
+    %94:vec3<f32> = select %93, %88, %85
+    ret %94
   }
 }
 )";
@@ -1177,6 +1241,13 @@
   gammaEncodeParams:tint_GammaTransferParams @offset(96)
   gamutConversionMatrix:mat3x3<f32> @offset(128)
   coordTransformationMatrix:mat3x2<f32> @offset(176)
+  loadTransformationMatrix:mat3x2<f32> @offset(200)
+  samplePlane0RectMin:vec2<f32> @offset(224)
+  samplePlane0RectMax:vec2<f32> @offset(232)
+  samplePlane1RectMin:vec2<f32> @offset(240)
+  samplePlane1RectMax:vec2<f32> @offset(248)
+  displayVisibleRectMax:vec2<u32> @offset(256)
+  plane1CoordFactor:vec2<f32> @offset(264)
 }
 
 $B1: {  # root
@@ -1212,69 +1283,79 @@
   $B3: {
     %29:u32 = access %params, 1u
     %30:mat3x4<f32> = access %params, 2u
-    %31:u32 = access %params, 0u
-    %32:bool = eq %31, 1u
-    %33:vec3<f32>, %34:f32 = if %32 [t: $B4, f: $B5] {  # if_1
+    %31:mat3x2<f32> = access %params, 7u
+    %32:vec2<u32> = access %params, 12u
+    %33:vec2<f32> = access %params, 13u
+    %34:vec2<u32> = min %coords_1, %32
+    %35:vec2<f32> = convert %34
+    %36:vec3<f32> = construct %35, 1.0f
+    %37:vec2<f32> = mul %31, %36
+    %38:vec2<f32> = round %37
+    %39:vec2<u32> = convert %38
+    %40:u32 = access %params, 0u
+    %41:bool = eq %40, 1u
+    %42:vec3<f32>, %43:f32 = if %41 [t: $B4, f: $B5] {  # if_1
       $B4: {  # true
-        %35:vec4<f32> = textureLoad %plane_0, %coords_1, 0u
-        %36:vec3<f32> = swizzle %35, xyz
-        %37:f32 = access %35, 3u
-        exit_if %36, %37  # if_1
+        %44:vec4<f32> = textureLoad %plane_0, %39, 0u
+        %45:vec3<f32> = swizzle %44, xyz
+        %46:f32 = access %44, 3u
+        exit_if %45, %46  # if_1
       }
       $B5: {  # false
-        %38:vec4<f32> = textureLoad %plane_0, %coords_1, 0u
-        %39:f32 = access %38, 0u
-        %40:vec2<u32> = shr %coords_1, vec2<u32>(1u)
-        %41:vec4<f32> = textureLoad %plane_1, %40, 0u
-        %42:vec2<f32> = swizzle %41, xy
-        %43:vec4<f32> = construct %39, %42, 1.0f
-        %44:vec3<f32> = mul %43, %30
-        exit_if %44, 1.0f  # if_1
+        %47:vec4<f32> = textureLoad %plane_0, %39, 0u
+        %48:f32 = access %47, 0u
+        %49:vec2<f32> = mul %38, %33
+        %50:vec2<u32> = convert %49
+        %51:vec4<f32> = textureLoad %plane_1, %50, 0u
+        %52:vec2<f32> = swizzle %51, xy
+        %53:vec4<f32> = construct %48, %52, 1.0f
+        %54:vec3<f32> = mul %53, %30
+        exit_if %54, 1.0f  # if_1
       }
     }
-    %45:bool = eq %29, 0u
-    %46:vec3<f32> = if %45 [t: $B6, f: $B7] {  # if_2
+    %55:bool = eq %29, 0u
+    %56:vec3<f32> = if %55 [t: $B6, f: $B7] {  # if_2
       $B6: {  # true
-        %47:tint_GammaTransferParams = access %params, 3u
-        %48:tint_GammaTransferParams = access %params, 4u
-        %49:mat3x3<f32> = access %params, 5u
-        %50:vec3<f32> = call %tint_GammaCorrection, %33, %47
-        %52:vec3<f32> = mul %49, %50
-        %53:vec3<f32> = call %tint_GammaCorrection, %52, %48
-        exit_if %53  # if_2
+        %57:tint_GammaTransferParams = access %params, 3u
+        %58:tint_GammaTransferParams = access %params, 4u
+        %59:mat3x3<f32> = access %params, 5u
+        %60:vec3<f32> = call %tint_GammaCorrection, %42, %57
+        %62:vec3<f32> = mul %59, %60
+        %63:vec3<f32> = call %tint_GammaCorrection, %62, %58
+        exit_if %63  # if_2
       }
       $B7: {  # false
-        exit_if %33  # if_2
+        exit_if %42  # if_2
       }
     }
-    %54:vec4<f32> = construct %46, %34
-    ret %54
+    %64:vec4<f32> = construct %56, %43
+    ret %64
   }
 }
 %tint_GammaCorrection = func(%v:vec3<f32>, %params_1:tint_GammaTransferParams):vec3<f32> {  # %params_1: 'params'
   $B8: {
-    %57:f32 = access %params_1, 0u
-    %58:f32 = access %params_1, 1u
-    %59:f32 = access %params_1, 2u
-    %60:f32 = access %params_1, 3u
-    %61:f32 = access %params_1, 4u
-    %62:f32 = access %params_1, 5u
-    %63:f32 = access %params_1, 6u
-    %64:vec3<f32> = construct %57
-    %65:vec3<f32> = construct %61
-    %66:vec3<f32> = abs %v
-    %67:vec3<f32> = sign %v
-    %68:vec3<bool> = lt %66, %65
-    %69:vec3<f32> = mul %60, %66
-    %70:vec3<f32> = add %69, %63
-    %71:vec3<f32> = mul %67, %70
-    %72:vec3<f32> = mul %58, %66
-    %73:vec3<f32> = add %72, %59
-    %74:vec3<f32> = pow %73, %64
-    %75:vec3<f32> = add %74, %62
-    %76:vec3<f32> = mul %67, %75
-    %77:vec3<f32> = select %76, %71, %68
-    ret %77
+    %67:f32 = access %params_1, 0u
+    %68:f32 = access %params_1, 1u
+    %69:f32 = access %params_1, 2u
+    %70:f32 = access %params_1, 3u
+    %71:f32 = access %params_1, 4u
+    %72:f32 = access %params_1, 5u
+    %73:f32 = access %params_1, 6u
+    %74:vec3<f32> = construct %67
+    %75:vec3<f32> = construct %71
+    %76:vec3<f32> = abs %v
+    %77:vec3<f32> = sign %v
+    %78:vec3<bool> = lt %76, %75
+    %79:vec3<f32> = mul %70, %76
+    %80:vec3<f32> = add %79, %73
+    %81:vec3<f32> = mul %77, %80
+    %82:vec3<f32> = mul %68, %76
+    %83:vec3<f32> = add %82, %69
+    %84:vec3<f32> = pow %83, %74
+    %85:vec3<f32> = add %84, %72
+    %86:vec3<f32> = mul %77, %85
+    %87:vec3<f32> = select %86, %81, %78
+    ret %87
   }
 }
 )";
diff --git a/src/tint/lang/core/ir/transform/preserve_padding_test.cc b/src/tint/lang/core/ir/transform/preserve_padding_test.cc
index 83f5942..188029c 100644
--- a/src/tint/lang/core/ir/transform/preserve_padding_test.cc
+++ b/src/tint/lang/core/ir/transform/preserve_padding_test.cc
@@ -661,19 +661,19 @@
         next_iteration 0u  # -> $B5
       }
       $B5 (%idx:u32): {  # body
-        %9:bool = gte %idx:u32, 4u
+        %9:bool = gte %idx, 4u
         if %9 [t: $B7] {  # if_1
           $B7: {  # true
             exit_loop  # loop_1
           }
         }
-        %10:ptr<storage, MyStruct, read_write> = access %target, %idx:u32
-        %11:MyStruct = access %value_param, %idx:u32
+        %10:ptr<storage, MyStruct, read_write> = access %target, %idx
+        %11:MyStruct = access %value_param, %idx
         %12:void = call %tint_store_and_preserve_padding_1, %10, %11
         continue  # -> $B6
       }
       $B6: {  # continuing
-        %14:u32 = add %idx:u32, 1u
+        %14:u32 = add %idx, 1u
         next_iteration %14  # -> $B5
       }
     }
@@ -893,19 +893,19 @@
         next_iteration 0u  # -> $B5
       }
       $B5 (%idx:u32): {  # body
-        %9:bool = gte %idx:u32, 4u
+        %9:bool = gte %idx, 4u
         if %9 [t: $B7] {  # if_1
           $B7: {  # true
             exit_loop  # loop_1
           }
         }
-        %10:ptr<storage, mat3x3<f32>, read_write> = access %target, %idx:u32
-        %11:mat3x3<f32> = access %value_param, %idx:u32
+        %10:ptr<storage, mat3x3<f32>, read_write> = access %target, %idx
+        %11:mat3x3<f32> = access %value_param, %idx
         %12:void = call %tint_store_and_preserve_padding_1, %10, %11
         continue  # -> $B6
       }
       $B6: {  # continuing
-        %14:u32 = add %idx:u32, 1u
+        %14:u32 = add %idx, 1u
         next_iteration %14  # -> $B5
       }
     }
@@ -980,19 +980,19 @@
         next_iteration 0u  # -> $B5
       }
       $B5 (%idx:u32): {  # body
-        %9:bool = gte %idx:u32, 4u
+        %9:bool = gte %idx, 4u
         if %9 [t: $B7] {  # if_1
           $B7: {  # true
             exit_loop  # loop_1
           }
         }
-        %10:ptr<storage, vec3<f32>, read_write> = access %target, %idx:u32
-        %11:vec3<f32> = access %value_param, %idx:u32
+        %10:ptr<storage, vec3<f32>, read_write> = access %target, %idx
+        %11:vec3<f32> = access %value_param, %idx
         store %10, %11
         continue  # -> $B6
       }
       $B6: {  # continuing
-        %12:u32 = add %idx:u32, 1u
+        %12:u32 = add %idx, 1u
         next_iteration %12  # -> $B5
       }
     }
@@ -1095,19 +1095,19 @@
         next_iteration 0u  # -> $B5
       }
       $B5 (%idx:u32): {  # body
-        %9:bool = gte %idx:u32, 3u
+        %9:bool = gte %idx, 3u
         if %9 [t: $B7] {  # if_1
           $B7: {  # true
             exit_loop  # loop_1
           }
         }
-        %10:ptr<storage, Outer, read_write> = access %target, %idx:u32
-        %11:Outer = access %value_param, %idx:u32
+        %10:ptr<storage, Outer, read_write> = access %target, %idx
+        %11:Outer = access %value_param, %idx
         %12:void = call %tint_store_and_preserve_padding_1, %10, %11
         continue  # -> $B6
       }
       $B6: {  # continuing
-        %14:u32 = add %idx:u32, 1u
+        %14:u32 = add %idx, 1u
         next_iteration %14  # -> $B5
       }
     }
@@ -1155,19 +1155,19 @@
         next_iteration 0u  # -> $B12
       }
       $B12 (%idx_1:u32): {  # body
-        %46:bool = gte %idx_1:u32, 4u
+        %46:bool = gte %idx_1, 4u
         if %46 [t: $B14] {  # if_2
           $B14: {  # true
             exit_loop  # loop_2
           }
         }
-        %47:ptr<storage, vec3<f32>, read_write> = access %target_3, %idx_1:u32
-        %48:vec3<f32> = access %value_param_3, %idx_1:u32
+        %47:ptr<storage, vec3<f32>, read_write> = access %target_3, %idx_1
+        %48:vec3<f32> = access %value_param_3, %idx_1
         store %47, %48
         continue  # -> $B13
       }
       $B13: {  # continuing
-        %49:u32 = add %idx_1:u32, 1u
+        %49:u32 = add %idx_1, 1u
         next_iteration %49  # -> $B12
       }
     }
@@ -1195,19 +1195,19 @@
         next_iteration 0u  # -> $B18
       }
       $B18 (%idx_2:u32): {  # body
-        %61:bool = gte %idx_2:u32, 4u
+        %61:bool = gte %idx_2, 4u
         if %61 [t: $B20] {  # if_3
           $B20: {  # true
             exit_loop  # loop_3
           }
         }
-        %62:ptr<storage, Inner, read_write> = access %target_5, %idx_2:u32
-        %63:Inner = access %value_param_5, %idx_2:u32
+        %62:ptr<storage, Inner, read_write> = access %target_5, %idx_2
+        %63:Inner = access %value_param_5, %idx_2
         %64:void = call %tint_store_and_preserve_padding_2, %62, %63
         continue  # -> $B19
       }
       $B19: {  # continuing
-        %65:u32 = add %idx_2:u32, 1u
+        %65:u32 = add %idx_2, 1u
         next_iteration %65  # -> $B18
       }
     }
diff --git a/src/tint/lang/core/ir/transform/std140_test.cc b/src/tint/lang/core/ir/transform/std140_test.cc
index 4b819bb..dd55108 100644
--- a/src/tint/lang/core/ir/transform/std140_test.cc
+++ b/src/tint/lang/core/ir/transform/std140_test.cc
@@ -565,20 +565,20 @@
         next_iteration 0u  # -> $B5
       }
       $B5 (%idx:u32): {  # body
-        %10:bool = gte %idx:u32, 4u
+        %10:bool = gte %idx, 4u
         if %10 [t: $B7] {  # if_1
           $B7: {  # true
             exit_loop  # loop_1
           }
         }
-        %11:ptr<function, Inner, read_write> = access %8, %idx:u32
-        %12:Inner_std140 = access %7, %idx:u32
+        %11:ptr<function, Inner, read_write> = access %8, %idx
+        %12:Inner_std140 = access %7, %idx
         %13:Inner = call %convert_Inner, %12
         store %11, %13
         continue  # -> $B6
       }
       $B6: {  # continuing
-        %15:u32 = add %idx:u32, 1u
+        %15:u32 = add %idx, 1u
         next_iteration %15  # -> $B5
       }
     }
@@ -906,20 +906,20 @@
         next_iteration 0u  # -> $B4
       }
       $B4 (%idx:u32): {  # body
-        %16:bool = gte %idx:u32, 4u
+        %16:bool = gte %idx, 4u
         if %16 [t: $B6] {  # if_1
           $B6: {  # true
             exit_loop  # loop_1
           }
         }
-        %17:ptr<function, Inner, read_write> = access %14, %idx:u32
-        %18:Inner_std140 = access %13, %idx:u32
+        %17:ptr<function, Inner, read_write> = access %14, %idx
+        %18:Inner_std140 = access %13, %idx
         %19:Inner = call %convert_Inner, %18
         store %17, %19
         continue  # -> $B5
       }
       $B5: {  # continuing
-        %21:u32 = add %idx:u32, 1u
+        %21:u32 = add %idx, 1u
         next_iteration %21  # -> $B4
       }
     }
@@ -1081,20 +1081,20 @@
         next_iteration 0u  # -> $B4
       }
       $B4 (%idx:u32): {  # body
-        %16:bool = gte %idx:u32, 4u
+        %16:bool = gte %idx, 4u
         if %16 [t: $B6] {  # if_1
           $B6: {  # true
             exit_loop  # loop_1
           }
         }
-        %17:ptr<function, Inner, read_write> = access %14, %idx:u32
-        %18:Inner_std140 = access %13, %idx:u32
+        %17:ptr<function, Inner, read_write> = access %14, %idx
+        %18:Inner_std140 = access %13, %idx
         %19:Inner = call %convert_Inner, %18
         store %17, %19
         continue  # -> $B5
       }
       $B5: {  # continuing
-        %21:u32 = add %idx:u32, 1u
+        %21:u32 = add %idx, 1u
         next_iteration %21  # -> $B4
       }
     }
@@ -1255,20 +1255,20 @@
         next_iteration 0u  # -> $B4
       }
       $B4 (%idx:u32): {  # body
-        %19:bool = gte %idx:u32, 4u
+        %19:bool = gte %idx, 4u
         if %19 [t: $B6] {  # if_1
           $B6: {  # true
             exit_loop  # loop_1
           }
         }
-        %20:ptr<function, Inner, read_write> = access %17, %idx:u32
-        %21:Inner_std140 = access %16, %idx:u32
+        %20:ptr<function, Inner, read_write> = access %17, %idx
+        %21:Inner_std140 = access %16, %idx
         %22:Inner = call %convert_Inner, %21
         store %20, %22
         continue  # -> $B5
       }
       $B5: {  # continuing
-        %24:u32 = add %idx:u32, 1u
+        %24:u32 = add %idx, 1u
         next_iteration %24  # -> $B4
       }
     }
diff --git a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
index b8c7703..37bdb03 100644
--- a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
+++ b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
@@ -54,7 +54,6 @@
     /// Process the module.
     void Process() {
         // Find and replace matrix constructors that take scalar operands.
-        Vector<Construct*, 8> worklist;
         for (auto inst : ir.Instructions()) {
             if (auto* construct = inst->As<Construct>()) {
                 if (construct->Result(0)->Type()->As<type::Matrix>()) {
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
index eeef8b2..8390ff6 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
@@ -292,11 +292,9 @@
         }
 
         // No local invocation index was found, so add one to the parameter list and use that.
-        Vector<FunctionParam*, 4> params = func->Params();
         auto* param = b.FunctionParam("tint_local_index", ty.u32());
+        func->AppendParam(param);
         param->SetBuiltin(BuiltinValue::kLocalInvocationIndex);
-        params.Push(param);
-        func->SetParams(params);
         return param;
     }
 
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
index 0e9c0b1..bf1a82b 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
@@ -480,18 +480,18 @@
         next_iteration %tint_local_index  # -> $B4
       }
       $B4 (%idx:u32): {  # body
-        %5:bool = gte %idx:u32, 4u
+        %5:bool = gte %idx, 4u
         if %5 [t: $B6] {  # if_1
           $B6: {  # true
             exit_loop  # loop_1
           }
         }
-        %6:ptr<workgroup, i32, read_write> = access %wgvar, %idx:u32
+        %6:ptr<workgroup, i32, read_write> = access %wgvar, %idx
         store %6, 0i
         continue  # -> $B5
       }
       $B5: {  # continuing
-        %7:u32 = add %idx:u32, 66u
+        %7:u32 = add %idx, 66u
         next_iteration %7  # -> $B4
       }
     }
@@ -542,20 +542,20 @@
         next_iteration %tint_local_index  # -> $B4
       }
       $B4 (%idx:u32): {  # body
-        %5:bool = gte %idx:u32, 35u
+        %5:bool = gte %idx, 35u
         if %5 [t: $B6] {  # if_1
           $B6: {  # true
             exit_loop  # loop_1
           }
         }
-        %6:u32 = mod %idx:u32, 5u
-        %7:u32 = div %idx:u32, 5u
+        %6:u32 = mod %idx, 5u
+        %7:u32 = div %idx, 5u
         %8:ptr<workgroup, u32, read_write> = access %wgvar, %7, %6
         store %8, 0u
         continue  # -> $B5
       }
       $B5: {  # continuing
-        %9:u32 = add %idx:u32, 66u
+        %9:u32 = add %idx, 66u
         next_iteration %9  # -> $B4
       }
     }
@@ -606,22 +606,22 @@
         next_iteration %tint_local_index  # -> $B4
       }
       $B4 (%idx:u32): {  # body
-        %5:bool = gte %idx:u32, 105u
+        %5:bool = gte %idx, 105u
         if %5 [t: $B6] {  # if_1
           $B6: {  # true
             exit_loop  # loop_1
           }
         }
-        %6:u32 = mod %idx:u32, 7u
-        %7:u32 = div %idx:u32, 7u
+        %6:u32 = mod %idx, 7u
+        %7:u32 = div %idx, 7u
         %8:u32 = mod %7, 5u
-        %9:u32 = div %idx:u32, 35u
+        %9:u32 = div %idx, 35u
         %10:ptr<workgroup, i32, read_write> = access %wgvar, %9, %8, %6
         store %10, 0i
         continue  # -> $B5
       }
       $B5: {  # continuing
-        %11:u32 = add %idx:u32, 1u
+        %11:u32 = add %idx, 1u
         next_iteration %11  # -> $B4
       }
     }
@@ -672,20 +672,20 @@
         next_iteration %tint_local_index  # -> $B4
       }
       $B4 (%idx:u32): {  # body
-        %5:bool = gte %idx:u32, 15u
+        %5:bool = gte %idx, 15u
         if %5 [t: $B6] {  # if_1
           $B6: {  # true
             exit_loop  # loop_1
           }
         }
-        %6:u32 = mod %idx:u32, 5u
-        %7:u32 = div %idx:u32, 5u
+        %6:u32 = mod %idx, 5u
+        %7:u32 = div %idx, 5u
         %8:ptr<workgroup, i32, read_write> = access %wgvar, %7, %6, 0u
         store %8, 0i
         continue  # -> $B5
       }
       $B5: {  # continuing
-        %9:u32 = add %idx:u32, 1u
+        %9:u32 = add %idx, 1u
         next_iteration %9  # -> $B4
       }
     }
@@ -736,20 +736,20 @@
         next_iteration %tint_local_index  # -> $B4
       }
       $B4 (%idx:u32): {  # body
-        %5:bool = gte %idx:u32, 15u
+        %5:bool = gte %idx, 15u
         if %5 [t: $B6] {  # if_1
           $B6: {  # true
             exit_loop  # loop_1
           }
         }
-        %6:u32 = mod %idx:u32, 3u
-        %7:u32 = div %idx:u32, 3u
+        %6:u32 = mod %idx, 3u
+        %7:u32 = div %idx, 3u
         %8:ptr<workgroup, i32, read_write> = access %wgvar, %7, 0u, %6
         store %8, 0i
         continue  # -> $B5
       }
       $B5: {  # continuing
-        %9:u32 = add %idx:u32, 1u
+        %9:u32 = add %idx, 1u
         next_iteration %9  # -> $B4
       }
     }
@@ -800,20 +800,20 @@
         next_iteration %tint_local_index  # -> $B4
       }
       $B4 (%idx:u32): {  # body
-        %5:bool = gte %idx:u32, 15u
+        %5:bool = gte %idx, 15u
         if %5 [t: $B6] {  # if_1
           $B6: {  # true
             exit_loop  # loop_1
           }
         }
-        %6:u32 = mod %idx:u32, 3u
-        %7:u32 = div %idx:u32, 3u
+        %6:u32 = mod %idx, 3u
+        %7:u32 = div %idx, 3u
         %8:ptr<workgroup, i32, read_write> = access %wgvar, 0u, %7, %6
         store %8, 0i
         continue  # -> $B5
       }
       $B5: {  # continuing
-        %9:u32 = add %idx:u32, 1u
+        %9:u32 = add %idx, 1u
         next_iteration %9  # -> $B4
       }
     }
@@ -1179,20 +1179,20 @@
         next_iteration %tint_local_index  # -> $B4
       }
       $B4 (%idx:u32): {  # body
-        %5:bool = gte %idx:u32, 7u
+        %5:bool = gte %idx, 7u
         if %5 [t: $B6] {  # if_1
           $B6: {  # true
             exit_loop  # loop_1
           }
         }
-        %6:ptr<workgroup, f32, read_write> = access %wgvar, %idx:u32, 0u
+        %6:ptr<workgroup, f32, read_write> = access %wgvar, %idx, 0u
         store %6, 0.0f
-        %7:ptr<workgroup, bool, read_write> = access %wgvar, %idx:u32, 2u
+        %7:ptr<workgroup, bool, read_write> = access %wgvar, %idx, 2u
         store %7, false
         continue  # -> $B5
       }
       $B5: {  # continuing
-        %8:u32 = add %idx:u32, 42u
+        %8:u32 = add %idx, 42u
         next_iteration %8  # -> $B4
       }
     }
@@ -1201,24 +1201,24 @@
         next_iteration %tint_local_index  # -> $B8
       }
       $B8 (%idx_1:u32): {  # body
-        %10:bool = gte %idx_1:u32, 91u
+        %10:bool = gte %idx_1, 91u
         if %10 [t: $B10] {  # if_2
           $B10: {  # true
             exit_loop  # loop_2
           }
         }
-        %11:u32 = mod %idx_1:u32, 13u
-        %12:u32 = div %idx_1:u32, 13u
+        %11:u32 = mod %idx_1, 13u
+        %12:u32 = div %idx_1, 13u
         %13:ptr<workgroup, i32, read_write> = access %wgvar, %12, 1u, %11, 0u
         store %13, 0i
-        %14:u32 = mod %idx_1:u32, 13u
-        %15:u32 = div %idx_1:u32, 13u
+        %14:u32 = mod %idx_1, 13u
+        %15:u32 = div %idx_1, 13u
         %16:ptr<workgroup, atomic<u32>, read_write> = access %wgvar, %15, 1u, %14, 1u
         %17:void = atomicStore %16, 0u
         continue  # -> $B9
       }
       $B9: {  # continuing
-        %18:u32 = add %idx_1:u32, 42u
+        %18:u32 = add %idx_1, 42u
         next_iteration %18  # -> $B8
       }
     }
@@ -1286,18 +1286,18 @@
         next_iteration %tint_local_index  # -> $B5
       }
       $B5 (%idx:u32): {  # body
-        %8:bool = gte %idx:u32, 4u
+        %8:bool = gte %idx, 4u
         if %8 [t: $B7] {  # if_2
           $B7: {  # true
             exit_loop  # loop_1
           }
         }
-        %9:ptr<workgroup, i32, read_write> = access %var_b, %idx:u32
+        %9:ptr<workgroup, i32, read_write> = access %var_b, %idx
         store %9, 0i
         continue  # -> $B6
       }
       $B6: {  # continuing
-        %10:u32 = add %idx:u32, 66u
+        %10:u32 = add %idx, 66u
         next_iteration %10  # -> $B5
       }
     }
@@ -1306,20 +1306,20 @@
         next_iteration %tint_local_index  # -> $B9
       }
       $B9 (%idx_1:u32): {  # body
-        %12:bool = gte %idx_1:u32, 35u
+        %12:bool = gte %idx_1, 35u
         if %12 [t: $B11] {  # if_3
           $B11: {  # true
             exit_loop  # loop_2
           }
         }
-        %13:u32 = mod %idx_1:u32, 5u
-        %14:u32 = div %idx_1:u32, 5u
+        %13:u32 = mod %idx_1, 5u
+        %14:u32 = div %idx_1, 5u
         %15:ptr<workgroup, u32, read_write> = access %var_c, %14, %13
         store %15, 0u
         continue  # -> $B10
       }
       $B10: {  # continuing
-        %16:u32 = add %idx_1:u32, 66u
+        %16:u32 = add %idx_1, 66u
         next_iteration %16  # -> $B9
       }
     }
@@ -1395,22 +1395,22 @@
         next_iteration %tint_local_index  # -> $B5
       }
       $B5 (%idx:u32): {  # body
-        %9:bool = gte %idx:u32, 42u
+        %9:bool = gte %idx, 42u
         if %9 [t: $B7] {  # if_2
           $B7: {  # true
             exit_loop  # loop_1
           }
         }
-        %10:ptr<workgroup, i32, read_write> = access %var_c, %idx:u32
+        %10:ptr<workgroup, i32, read_write> = access %var_c, %idx
         store %10, 0i
-        %11:u32 = mod %idx:u32, 6u
-        %12:u32 = div %idx:u32, 6u
+        %11:u32 = mod %idx, 6u
+        %12:u32 = div %idx, 6u
         %13:ptr<workgroup, u32, read_write> = access %var_d, %12, %11
         store %13, 0u
         continue  # -> $B6
       }
       $B6: {  # continuing
-        %14:u32 = add %idx:u32, 66u
+        %14:u32 = add %idx, 66u
         next_iteration %14  # -> $B5
       }
     }
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index c9db3d3..e945cc6 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -32,23 +32,26 @@
 #include <string>
 #include <utility>
 
-#include "src/tint/lang/core/fluent_types.h"
 #include "src/tint/lang/core/intrinsic/table.h"
 #include "src/tint/lang/core/ir/access.h"
 #include "src/tint/lang/core/ir/binary.h"
 #include "src/tint/lang/core/ir/bitcast.h"
+#include "src/tint/lang/core/ir/block_param.h"
 #include "src/tint/lang/core/ir/break_if.h"
+#include "src/tint/lang/core/ir/constant.h"
 #include "src/tint/lang/core/ir/construct.h"
 #include "src/tint/lang/core/ir/continue.h"
 #include "src/tint/lang/core/ir/convert.h"
 #include "src/tint/lang/core/ir/core_builtin_call.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/core/ir/discard.h"
 #include "src/tint/lang/core/ir/exit_if.h"
 #include "src/tint/lang/core/ir/exit_loop.h"
 #include "src/tint/lang/core/ir/exit_switch.h"
 #include "src/tint/lang/core/ir/function.h"
+#include "src/tint/lang/core/ir/function_param.h"
 #include "src/tint/lang/core/ir/if.h"
+#include "src/tint/lang/core/ir/instruction_result.h"
 #include "src/tint/lang/core/ir/let.h"
 #include "src/tint/lang/core/ir/load.h"
 #include "src/tint/lang/core/ir/load_vector_element.h"
@@ -72,16 +75,21 @@
 #include "src/tint/lang/core/type/type.h"
 #include "src/tint/lang/core/type/vector.h"
 #include "src/tint/lang/core/type/void.h"
+#include "src/tint/utils/containers/hashset.h"
 #include "src/tint/utils/containers/reverse.h"
 #include "src/tint/utils/containers/transform.h"
+#include "src/tint/utils/ice/ice.h"
+#include "src/tint/utils/macros/defer.h"
 #include "src/tint/utils/macros/scoped_assignment.h"
 #include "src/tint/utils/rtti/switch.h"
+#include "src/tint/utils/text/styled_text.h"
 #include "src/tint/utils/text/text_style.h"
 
 /// If set to 1 then the Tint will dump the IR when validating.
 #define TINT_DUMP_IR_WHEN_VALIDATING 0
 #if TINT_DUMP_IR_WHEN_VALIDATING
 #include <iostream>
+#include "src/tint/utils/text/styled_text_printer.h"
 #endif
 
 using namespace tint::core::fluent_types;  // NOLINT
@@ -139,7 +147,10 @@
     /// @returns success or failure
     Result<SuccessType> Run();
 
-  protected:
+  private:
+    /// @returns the IR disassembly, performing a disassemble if this is the first call.
+    ir::Disassembly& Disassembly();
+
     /// Adds an error for the @p inst and highlights the instruction in the disassembly
     /// @param inst the instruction
     /// @returns the diagnostic
@@ -147,7 +158,7 @@
 
     /// Adds an error for the @p inst operand at @p idx and highlights the operand in the
     /// disassembly
-    /// @param inst the instaruction
+    /// @param inst the instruction
     /// @param idx the operand index
     /// @returns the diagnostic
     diag::Diagnostic& AddError(const Instruction* inst, size_t idx);
@@ -191,11 +202,15 @@
     /// @param func the function
     diag::Diagnostic& AddNote(const Function* func);
 
-    /// Adds a note to @p inst for operand @p idx and highlights the operand in the
-    /// disassembly
+    /// Adds a note to @p inst for operand @p idx and highlights the operand in the disassembly
     /// @param inst the instruction
     /// @param idx the operand index
-    diag::Diagnostic& AddNote(const Instruction* inst, size_t idx);
+    diag::Diagnostic& AddOperandNote(const Instruction* inst, size_t idx);
+
+    /// Adds a note to @p inst for result @p idx and highlights the result in the disassembly
+    /// @param inst the instruction
+    /// @param idx the result index
+    diag::Diagnostic& AddResultNote(const Instruction* inst, size_t idx);
 
     /// Adds a note to @p blk and highlights the block in the disassembly
     /// @param blk the block
@@ -205,9 +220,14 @@
     /// @param src the source lines to highlight
     diag::Diagnostic& AddNote(Source src = {});
 
+    /// Adds a note to the diagnostics highlighting where the value was declared, if it has a source
+    /// location.
+    /// @param value the value
+    void AddDeclarationNote(const Value* value);
+
     /// @param v the value to get the name for
     /// @returns the name for the given value
-    std::string Name(const Value* v);
+    StyledText NameOf(const Value* v);
 
     /// Checks the given operand is not null
     /// @param inst the instruction
@@ -231,10 +251,6 @@
     /// @param func the function validate
     void CheckFunction(const Function* func);
 
-    /// Validates the given block
-    /// @param blk the block to validate
-    void CheckBlock(const Block* blk);
-
     /// Validates the given instruction
     /// @param inst the instruction to validate
     void CheckInstruction(const Instruction* inst);
@@ -334,18 +350,50 @@
     /// @returns the vector pointer type for the given instruction operand
     const core::type::Type* GetVectorPtrElementType(const Instruction* inst, size_t idx);
 
-  private:
+    /// Executes all the pending tasks
+    void ProcessTasks();
+
+    /// Queues the block to be validated with ProcessTasks()
+    /// @param blk the block to validate
+    void QueueBlock(const Block* blk);
+
+    /// Queues the list of instructions starting with @p inst to be validated
+    /// @param inst the first instruction
+    void QueueInstructions(const Instruction* inst);
+
+    /// Begins validation of the block @p blk, and its instructions.
+    /// BeginBlock() pushes a new scope for values.
+    /// Must be paired with a call to EndBlock().
+    void BeginBlock(const Block* blk);
+
+    /// Ends validation of the block opened with BeginBlock() and closes the block's scope for
+    /// values.
+    void EndBlock();
+
+    /// ScopeStack holds a stack of values that are currently in scope
+    struct ScopeStack {
+        void Push() { stack_.Push({}); }
+        void Pop() { stack_.Pop(); }
+        void Add(const Value* value) { stack_.Back().Add(value); }
+        bool Contains(const Value* value) {
+            return stack_.Any([&](auto& v) { return v.Contains(value); });
+        }
+        bool IsEmpty() const { return stack_.IsEmpty(); }
+
+      private:
+        Vector<Hashset<const Value*, 8>, 4> stack_;
+    };
+
     const Module& mod_;
     Capabilities capabilities_;
-    std::shared_ptr<Source::File> disassembly_file;
+    std::optional<ir::Disassembly> disassembly_;  // Use Disassembly()
     diag::List diagnostics_;
-    Disassembler dis_{mod_};
-    const Block* current_block_ = nullptr;
     Hashset<const Function*, 4> all_functions_;
     Hashset<const Instruction*, 4> visited_instructions_;
     Vector<const ControlInstruction*, 8> control_stack_;
-
-    void DisassembleIfNeeded();
+    Vector<const Block*, 8> block_stack_;
+    ScopeStack scope_stack_;
+    Vector<std::function<void()>, 16> tasks_;
 };
 
 Validator::Validator(const Module& mod, Capabilities capabilities)
@@ -353,21 +401,30 @@
 
 Validator::~Validator() = default;
 
-void Validator::DisassembleIfNeeded() {
-    if (disassembly_file) {
-        return;
+Disassembly& Validator::Disassembly() {
+    if (!disassembly_) {
+        disassembly_.emplace(Disassemble(mod_));
     }
-    disassembly_file = std::make_unique<Source::File>("", dis_.Disassemble().Plain());
+    return *disassembly_;
 }
 
 Result<SuccessType> Validator::Run() {
+    scope_stack_.Push();
+    TINT_DEFER({
+        scope_stack_.Pop();
+        TINT_ASSERT(scope_stack_.IsEmpty());
+        TINT_ASSERT(tasks_.IsEmpty());
+        TINT_ASSERT(control_stack_.IsEmpty());
+        TINT_ASSERT(block_stack_.IsEmpty());
+    });
     CheckRootBlock(mod_.root_block);
 
     for (auto& func : mod_.functions) {
         if (!all_functions_.Add(func.Get())) {
-            AddError(func) << "function " << style::Function(Name(func.Get()))
+            AddError(func) << "function " << NameOf(func.Get())
                            << " added to module multiple times";
         }
+        scope_stack_.Add(func);
     }
 
     for (auto& func : mod_.functions) {
@@ -384,116 +441,134 @@
     }
 
     if (diagnostics_.ContainsErrors()) {
-        DisassembleIfNeeded();
-        diagnostics_.AddNote(tint::diag::System::IR, Source{}) << "# Disassembly\n"
-                                                               << disassembly_file->content.data;
+        diagnostics_.AddNote(Source{}) << "# Disassembly\n" << Disassembly().Text();
         return Failure{std::move(diagnostics_)};
     }
     return Success;
 }
 
 diag::Diagnostic& Validator::AddError(const Instruction* inst) {
-    DisassembleIfNeeded();
-    auto src = dis_.InstructionSource(inst);
+    auto src = Disassembly().InstructionSource(inst);
     auto& diag = AddError(src) << inst->FriendlyName() << ": ";
 
-    if (current_block_) {
-        AddNote(current_block_) << "In block";
+    if (!block_stack_.IsEmpty()) {
+        AddNote(block_stack_.Back()) << "in block";
     }
     return diag;
 }
 
 diag::Diagnostic& Validator::AddError(const Instruction* inst, size_t idx) {
-    DisassembleIfNeeded();
-    auto src = dis_.OperandSource(Disassembler::IndexedValue{inst, static_cast<uint32_t>(idx)});
+    auto src =
+        Disassembly().OperandSource(Disassembly::IndexedValue{inst, static_cast<uint32_t>(idx)});
     auto& diag = AddError(src) << inst->FriendlyName() << ": ";
 
-    if (current_block_) {
-        AddNote(current_block_) << "In block";
+    if (!block_stack_.IsEmpty()) {
+        AddNote(block_stack_.Back()) << "in block";
     }
-
     return diag;
 }
 
 diag::Diagnostic& Validator::AddResultError(const Instruction* inst, size_t idx) {
-    DisassembleIfNeeded();
-    auto src = dis_.ResultSource(Disassembler::IndexedValue{inst, static_cast<uint32_t>(idx)});
+    auto src =
+        Disassembly().ResultSource(Disassembly::IndexedValue{inst, static_cast<uint32_t>(idx)});
     auto& diag = AddError(src) << inst->FriendlyName() << ": ";
 
-    if (current_block_) {
-        AddNote(current_block_) << "In block";
+    if (!block_stack_.IsEmpty()) {
+        AddNote(block_stack_.Back()) << "in block";
     }
     return diag;
 }
 
 diag::Diagnostic& Validator::AddError(const Block* blk) {
-    DisassembleIfNeeded();
-    auto src = dis_.BlockSource(blk);
+    auto src = Disassembly().BlockSource(blk);
     return AddError(src);
 }
 
 diag::Diagnostic& Validator::AddError(const BlockParam* param) {
-    DisassembleIfNeeded();
-    auto src = dis_.BlockParamSource(param);
+    auto src = Disassembly().BlockParamSource(param);
     return AddError(src);
 }
 
 diag::Diagnostic& Validator::AddError(const Function* func) {
-    DisassembleIfNeeded();
-    auto src = dis_.FunctionSource(func);
+    auto src = Disassembly().FunctionSource(func);
     return AddError(src);
 }
 
 diag::Diagnostic& Validator::AddError(const FunctionParam* param) {
-    DisassembleIfNeeded();
-    auto src = dis_.FunctionParamSource(param);
+    auto src = Disassembly().FunctionParamSource(param);
     return AddError(src);
 }
 
 diag::Diagnostic& Validator::AddNote(const Instruction* inst) {
-    DisassembleIfNeeded();
-    auto src = dis_.InstructionSource(inst);
+    auto src = Disassembly().InstructionSource(inst);
     return AddNote(src);
 }
 
 diag::Diagnostic& Validator::AddNote(const Function* func) {
-    DisassembleIfNeeded();
-    auto src = dis_.FunctionSource(func);
+    auto src = Disassembly().FunctionSource(func);
     return AddNote(src);
 }
 
-diag::Diagnostic& Validator::AddNote(const Instruction* inst, size_t idx) {
-    DisassembleIfNeeded();
-    auto src = dis_.OperandSource(Disassembler::IndexedValue{inst, static_cast<uint32_t>(idx)});
+diag::Diagnostic& Validator::AddOperandNote(const Instruction* inst, size_t idx) {
+    auto src =
+        Disassembly().OperandSource(Disassembly::IndexedValue{inst, static_cast<uint32_t>(idx)});
+    return AddNote(src);
+}
+
+diag::Diagnostic& Validator::AddResultNote(const Instruction* inst, size_t idx) {
+    auto src =
+        Disassembly().ResultSource(Disassembly::IndexedValue{inst, static_cast<uint32_t>(idx)});
     return AddNote(src);
 }
 
 diag::Diagnostic& Validator::AddNote(const Block* blk) {
-    DisassembleIfNeeded();
-    auto src = dis_.BlockSource(blk);
+    auto src = Disassembly().BlockSource(blk);
     return AddNote(src);
 }
 
 diag::Diagnostic& Validator::AddError(Source src) {
-    auto& diag = diagnostics_.AddError(tint::diag::System::IR, src);
-    if (src.range != Source::Range{{}}) {
-        diag.source.file = disassembly_file.get();
-        diag.owned_file = disassembly_file;
-    }
+    auto& diag = diagnostics_.AddError(src);
+    diag.owned_file = Disassembly().File();
     return diag;
 }
 
 diag::Diagnostic& Validator::AddNote(Source src) {
-    auto& diag = diagnostics_.AddNote(tint::diag::System::IR, src);
-    if (src.range != Source::Range{{}}) {
-        diag.source.file = disassembly_file.get();
-        diag.owned_file = disassembly_file;
-    }
+    auto& diag = diagnostics_.AddNote(src);
+    diag.owned_file = Disassembly().File();
     return diag;
 }
 
-std::string Validator::Name(const Value* v) {
-    return mod_.NameOf(v).Name();
+void Validator::AddDeclarationNote(const Value* value) {
+    tint::Switch(
+        value,  //
+        [&](const InstructionResult* res) {
+            if (auto* inst = res->Instruction()) {
+                auto results = inst->Results();
+                for (size_t i = 0; i < results.Length(); i++) {
+                    if (results[i] == value) {
+                        AddResultNote(res->Instruction(), i) << NameOf(value) << " declared here";
+                        return;
+                    }
+                }
+            }
+        },
+        [&](const FunctionParam* param) {
+            auto src = Disassembly().FunctionParamSource(param);
+            if (src.file) {
+                AddNote(src) << NameOf(value) << " declared here";
+            }
+        },
+        [&](const BlockParam* param) {
+            auto src = Disassembly().BlockParamSource(param);
+            if (src.file) {
+                AddNote(src) << NameOf(value) << " declared here";
+            }
+        },
+        [&](const Function* fn) { AddNote(fn) << NameOf(value) << " declared here"; });
+}
+
+StyledText Validator::NameOf(const Value* value) {
+    return Disassembly().NameOf(value);
 }
 
 void Validator::CheckOperandNotNull(const Instruction* inst, const ir::Value* operand, size_t idx) {
@@ -512,7 +587,8 @@
 }
 
 void Validator::CheckRootBlock(const Block* blk) {
-    TINT_SCOPED_ASSIGNMENT(current_block_, blk);
+    block_stack_.Push(blk);
+    TINT_DEFER(block_stack_.Pop());
 
     for (auto* inst : *blk) {
         if (inst->Block() != blk) {
@@ -529,7 +605,9 @@
 }
 
 void Validator::CheckFunction(const Function* func) {
-    CheckBlock(func->Block());
+    // Scope holds the parameters and block
+    scope_stack_.Push();
+    TINT_DEFER(scope_stack_.Pop());
 
     for (auto* param : func->Params()) {
         if (!param->Alive()) {
@@ -549,14 +627,31 @@
         if (HoldsType<type::Reference>(param->Type())) {
             AddError(param) << "references are not permitted as parameter types";
         }
+
+        scope_stack_.Add(param);
     }
     if (HoldsType<type::Reference>(func->ReturnType())) {
         AddError(func) << "references are not permitted as return types";
     }
+
+    QueueBlock(func->Block());
+    ProcessTasks();
 }
 
-void Validator::CheckBlock(const Block* blk) {
-    TINT_SCOPED_ASSIGNMENT(current_block_, blk);
+void Validator::ProcessTasks() {
+    while (!tasks_.IsEmpty()) {
+        tasks_.Pop()();
+    }
+}
+
+void Validator::QueueBlock(const Block* blk) {
+    tasks_.Push([this] { EndBlock(); });
+    tasks_.Push([this, blk] { BeginBlock(blk); });
+}
+
+void Validator::BeginBlock(const Block* blk) {
+    scope_stack_.Push();
+    block_stack_.Push(blk);
 
     if (auto* mb = blk->As<MultiInBlock>()) {
         for (auto* param : mb->Params()) {
@@ -572,26 +667,45 @@
                 AddNote(param->Block()) << "parent block declared here";
                 return;
             }
+            scope_stack_.Add(param);
         }
     }
 
     if (!blk->Terminator()) {
-        AddError(blk) << "block: does not end in a terminator instruction";
+        AddError(blk) << "block does not end in a terminator instruction";
     }
 
+    // Validate the instructions w.r.t. the parent block
     for (auto* inst : *blk) {
         if (inst->Block() != blk) {
             AddError(inst) << "block instruction does not have same block as parent";
-            AddNote(current_block_) << "In block";
+            AddNote(blk) << "in block";
             continue;
         }
         if (inst->Is<ir::Terminator>() && inst != blk->Terminator()) {
-            AddError(inst) << "block: terminator which isn't the final instruction";
+            AddError(inst) << "block terminator which isn't the final instruction";
             continue;
         }
-
-        CheckInstruction(inst);
     }
+
+    // Enqueue validation of the instructions of the block
+    if (!blk->IsEmpty()) {
+        QueueInstructions(blk->Instructions());
+    }
+}
+
+void Validator::EndBlock() {
+    scope_stack_.Pop();
+    block_stack_.Pop();
+}
+
+void Validator::QueueInstructions(const Instruction* inst) {
+    tasks_.Push([this, inst] {
+        CheckInstruction(inst);
+        if (inst->next) {
+            QueueInstructions(inst->next);
+        }
+    });
 }
 
 void Validator::CheckInstruction(const Instruction* inst) {
@@ -632,10 +746,13 @@
         // for `nullptr` here.
         if (!op->Alive()) {
             AddError(inst, i) << "operand is not alive";
-        }
-
-        if (!op->HasUsage(inst, i)) {
+        } else if (!op->HasUsage(inst, i)) {
             AddError(inst, i) << "operand missing usage";
+        } else if (auto fn = op->As<Function>(); fn && !all_functions_.Contains(fn)) {
+            AddError(inst, i) << NameOf(op) << " is not part of the module";
+        } else if (!op->Is<Constant>() && !scope_stack_.Contains(op)) {
+            AddError(inst, i) << NameOf(op) << " is not in scope";
+            AddDeclarationNote(op);
         }
 
         if (!capabilities_.Contains(Capability::kAllowRefTypes)) {
@@ -663,6 +780,10 @@
         [&](const Unary* u) { CheckUnary(u); },                            //
         [&](const Var* var) { CheckVar(var); },                            //
         [&](const Default) { AddError(inst) << "missing validation"; });
+
+    for (auto* result : results) {
+        scope_stack_.Add(result);
+    }
 }
 
 void Validator::CheckVar(const Var* var) {
@@ -721,10 +842,6 @@
 }
 
 void Validator::CheckUserCall(const UserCall* call) {
-    if (!all_functions_.Contains(call->Target())) {
-        AddError(call, UserCall::kFunctionOperandOffset) << "call target is not part of the module";
-    }
-
     if (call->Target()->Stage() != Function::PipelineStage::kUndefined) {
         AddError(call, UserCall::kFunctionOperandOffset)
             << "call target must not have a pipeline stage";
@@ -778,7 +895,7 @@
             return AddError(a, i + Access::kIndicesOperandOffset);
         };
         auto note = [&]() -> diag::Diagnostic& {
-            return AddNote(a, i + Access::kIndicesOperandOffset);
+            return AddOperandNote(a, i + Access::kIndicesOperandOffset);
         };
 
         auto* index = a->Indices()[i];
@@ -912,36 +1029,50 @@
         AddError(if_, If::kConditionOperandOffset) << "condition must be a `bool` type";
     }
 
-    control_stack_.Push(if_);
-    TINT_DEFER(control_stack_.Pop());
+    tasks_.Push([this] { control_stack_.Pop(); });
 
-    CheckBlock(if_->True());
     if (!if_->False()->IsEmpty()) {
-        CheckBlock(if_->False());
+        QueueBlock(if_->False());
     }
+
+    QueueBlock(if_->True());
+
+    tasks_.Push([this, if_] { control_stack_.Push(if_); });
 }
 
 void Validator::CheckLoop(const Loop* l) {
-    control_stack_.Push(l);
-    TINT_DEFER(control_stack_.Pop());
-
+    // Note: Tasks are queued in reverse order of their execution
+    tasks_.Push([this] { control_stack_.Pop(); });
     if (!l->Initializer()->IsEmpty()) {
-        CheckBlock(l->Initializer());
+        tasks_.Push([this] { EndBlock(); });
     }
-    CheckBlock(l->Body());
+    tasks_.Push([this] { EndBlock(); });
+    if (!l->Continuing()->IsEmpty()) {
+        tasks_.Push([this] { EndBlock(); });
+    }
+
+    // ⎡Initializer              ⎤
+    // ⎢    ⎡Body               ⎤⎥
+    // ⎣    ⎣    [Continuing ]  ⎦⎦
 
     if (!l->Continuing()->IsEmpty()) {
-        CheckBlock(l->Continuing());
+        tasks_.Push([this, l] { BeginBlock(l->Continuing()); });
     }
+    tasks_.Push([this, l] { BeginBlock(l->Body()); });
+    if (!l->Initializer()->IsEmpty()) {
+        tasks_.Push([this, l] { BeginBlock(l->Initializer()); });
+    }
+    tasks_.Push([this, l] { control_stack_.Push(l); });
 }
 
 void Validator::CheckSwitch(const Switch* s) {
-    control_stack_.Push(s);
-    TINT_DEFER(control_stack_.Pop());
+    tasks_.Push([this] { control_stack_.Pop(); });
 
     for (auto& cse : s->Cases()) {
-        CheckBlock(cse.block);
+        QueueBlock(cse.block);
     }
+
+    tasks_.Push([this, s] { control_stack_.Push(s); });
 }
 
 void Validator::CheckTerminator(const Terminator* b) {
@@ -1169,10 +1300,11 @@
                                             [[maybe_unused]] const char* msg,
                                             [[maybe_unused]] Capabilities capabilities) {
 #if TINT_DUMP_IR_WHEN_VALIDATING
+    auto printer = StyledTextPrinter::Create(stdout);
     std::cout << "=========================================================" << std::endl;
     std::cout << "== IR dump before " << msg << ":" << std::endl;
     std::cout << "=========================================================" << std::endl;
-    std::cout << Disassemble(ir);
+    printer->Print(Disassemble(ir).Text());
 #endif
 
 #ifndef NDEBUG
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 16bb823..864e24d 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -71,7 +71,7 @@
   loop [b: $B2] {  # loop_1
   ^^^^^^^^^^^^^
 
-:1:1 note: In block
+:1:1 note: in block
 $B1: {  # root
 ^^^
 
@@ -102,7 +102,7 @@
   %1:ptr<private, i32, read_write> = var
                                      ^^^
 
-:1:1 note: In block
+:1:1 note: in block
 $B1: {  # root
 ^^^
 
@@ -139,7 +139,7 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
-              R"(:1:1 error: function 'my_func' added to module multiple times
+              R"(:1:1 error: function %my_func added to module multiple times
 %my_func = func(%2:i32, %3:f32):void {
 ^^^^^^^^
 
@@ -253,11 +253,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
-              R"(:3:20 error: call: call target is not part of the module
+              R"(:3:20 error: call: %g is not part of the module
     %2:void = call %g
                    ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -288,7 +288,7 @@
     %2:void = call %g
                    ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -325,7 +325,7 @@
     %5:void = call %g, 42i
                    ^^
 
-:7:3 note: In block
+:7:3 note: in block
   $B2: {
   ^^^
 
@@ -362,7 +362,7 @@
     %5:void = call %g, 1i, 2i, 3i
                    ^^
 
-:7:3 note: In block
+:7:3 note: in block
   $B2: {
   ^^^
 
@@ -399,7 +399,7 @@
     %6:void = call %g, 1i, 2.0f, 3i
                            ^^^^
 
-:7:3 note: In block
+:7:3 note: in block
   $B2: {
   ^^^
 
@@ -424,7 +424,7 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
-              R"(:2:3 error: block: does not end in a terminator instruction
+              R"(:2:3 error: block does not end in a terminator instruction
   $B1: {
   ^^^
 
@@ -455,11 +455,11 @@
     %2:ptr<function, i32, read_write> = var
                                         ^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -496,7 +496,7 @@
     EXPECT_EQ(res.Failure().reason.Str(),
               R"(:4:12 error: destroyed parameter found in block parameter list
       $B2 (%my_param:f32): {  # body
-           ^^^^^^^^^^^^^
+           ^^^^^^^^^
 
 note: # Disassembly
 %my_func = func():void {
@@ -530,7 +530,7 @@
     EXPECT_EQ(res.Failure().reason.Str(),
               R"(:4:12 error: block parameter has nullptr parent block
       $B2 (%my_param:f32): {  # body
-           ^^^^^^^^^^^^^
+           ^^^^^^^^^
 
 note: # Disassembly
 %my_func = func():void {
@@ -564,7 +564,7 @@
     EXPECT_EQ(res.Failure().reason.Str(),
               R"(:4:12 error: block parameter has incorrect parent block
       $B2 (%my_param:f32): {  # body
-           ^^^^^^^^^^^^^
+           ^^^^^^^^^
 
 :7:7 note: parent block declared here
       $B3 (%my_param:f32): {  # continuing
@@ -575,10 +575,10 @@
   $B1: {
     loop [b: $B2, c: $B3] {  # loop_1
       $B2 (%my_param:f32): {  # body
-        continue %my_param:f32  # -> $B3
+        continue %my_param  # -> $B3
       }
       $B3 (%my_param:f32): {  # continuing
-        next_iteration %my_param:f32  # -> $B2
+        next_iteration %my_param  # -> $B2
       }
     }
     ret
@@ -604,7 +604,7 @@
     %3:f32 = access %2, -1i
                         ^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -635,7 +635,7 @@
     %3:f32 = access %2, 1u, 3u
                             ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -671,7 +671,7 @@
     %3:ptr<private, f32, read_write> = access %2, 1u, 3u
                                                       ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -705,7 +705,7 @@
     %3:f32 = access %2, 1u
                         ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -736,7 +736,7 @@
     %3:ptr<private, f32, read_write> = access %2, 1u
                                                   ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -773,7 +773,7 @@
     %4:i32 = access %2, %3
                         ^^
 
-:7:3 note: In block
+:7:3 note: in block
   $B1: {
   ^^^
 
@@ -816,7 +816,7 @@
     %4:i32 = access %2, %3
                         ^^
 
-:7:3 note: In block
+:7:3 note: in block
   $B1: {
   ^^^
 
@@ -852,7 +852,7 @@
     %3:i32 = access %2, 1u, 1u
              ^^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -884,7 +884,7 @@
     %3:ptr<private, i32, read_write> = access %2, 1u, 1u
                                        ^^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -916,7 +916,7 @@
     %3:f32 = access %2, 1u, 1u
              ^^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -947,7 +947,7 @@
     %3:f32 = access %2, 1u
                         ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -992,7 +992,7 @@
     %3:f32 = access %2, 1u, 1u
                             ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1038,7 +1038,7 @@
     %3:ptr<uniform, f32, read> = access %2, 1u
                                  ^^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1070,7 +1070,7 @@
     %3:ptr<storage, f32, read_write> = access %2, 1u
                                        ^^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1123,11 +1123,11 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
-              R"(:3:5 error: return: block: terminator which isn't the final instruction
+              R"(:3:5 error: return: block terminator which isn't the final instruction
     ret
     ^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1165,7 +1165,7 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
-              R"(:4:7 error: block: does not end in a terminator instruction
+              R"(:4:7 error: block does not end in a terminator instruction
       $B2: {  # true
       ^^^
 
@@ -1201,7 +1201,7 @@
     if 1i [t: $B2, f: $B3] {  # if_1
        ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1238,7 +1238,7 @@
     if undef [t: $B2, f: $B3] {  # if_1
        ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1277,7 +1277,7 @@
     undef = if true [t: $B2, f: $B3] {  # if_1
     ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1321,7 +1321,7 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
-              R"(:4:7 error: block: does not end in a terminator instruction
+              R"(:4:7 error: block does not end in a terminator instruction
       $B2: {  # body
       ^^^
 
@@ -1348,7 +1348,7 @@
   undef = var
   ^^^^^
 
-:1:1 note: In block
+:1:1 note: in block
 $B1: {  # root
 ^^^
 
@@ -1375,7 +1375,7 @@
     undef = var
     ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1392,27 +1392,26 @@
 TEST_F(IR_ValidatorTest, Var_Init_WrongType) {
     auto* f = b.Function("my_func", ty.void_());
 
-    auto sb = b.Append(f->Block());
-    auto* v = sb.Var(ty.ptr<function, f32>());
-    sb.Return(f);
-
-    auto* result = sb.InstructionResult(ty.i32());
-    v->SetInitializer(result);
+    b.Append(f->Block(), [&] {
+        auto* v = b.Var<function, f32>();
+        v->SetInitializer(b.Constant(1_i));
+        b.Return(f);
+    });
 
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(), R"(:3:41 error: var: initializer has incorrect type
-    %2:ptr<function, f32, read_write> = var, %3
+    %2:ptr<function, f32, read_write> = var, 1i
                                         ^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
 note: # Disassembly
 %my_func = func():void {
   $B1: {
-    %2:ptr<function, f32, read_write> = var, %3
+    %2:ptr<function, f32, read_write> = var, 1i
     ret
   }
 }
@@ -1434,7 +1433,7 @@
     undef = let 1i
     ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1463,7 +1462,7 @@
     %2:f32 = let undef
                  ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1493,7 +1492,7 @@
     %2:f32 = let 1i
              ^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1524,7 +1523,7 @@
     <destroyed tint::core::ir::Var $ADDRESS>
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^$ARROWS^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1561,7 +1560,7 @@
     %2:ptr<function, f32, read_write> = var
     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1592,7 +1591,7 @@
     %2:ptr<function, f32, read_write> = var, %3
                                              ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1623,7 +1622,7 @@
     %2:ptr<function, f32, read_write> = var, %3
                                              ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1673,7 +1672,7 @@
     %2:i32 = add undef, 2i
                  ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1700,7 +1699,7 @@
     %2:i32 = add 2i, undef
                      ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1730,7 +1729,7 @@
     undef = add 3i, 2i
     ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1757,7 +1756,7 @@
     %2:i32 = negation undef
                       ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1787,7 +1786,7 @@
     undef = negation 2i
     ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1818,7 +1817,7 @@
     %2:f32 = complement 2i
     ^^^^^^^^^^^^^^^^^^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -1859,7 +1858,7 @@
         exit_if  # undef
         ^^^^^^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # true
       ^^^
 
@@ -1898,7 +1897,7 @@
         exit_if 1i  # if_1
         ^^^^^^^^^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # true
       ^^^
 
@@ -1942,7 +1941,7 @@
         exit_if 1i, 2.0f, 3i  # if_1
         ^^^^^^^^^^^^^^^^^^^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # true
       ^^^
 
@@ -2003,7 +2002,7 @@
         exit_if 1i, 2i  # if_1
                     ^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # true
       ^^^
 
@@ -2043,7 +2042,7 @@
     exit_if  # if_1
     ^^^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -2086,7 +2085,7 @@
             exit_if  # if_1
             ^^^^^^^
 
-:6:11 note: In block
+:6:11 note: in block
           $B3: {  # true
           ^^^
 
@@ -2139,7 +2138,7 @@
             exit_if  # if_1
             ^^^^^^^
 
-:6:11 note: In block
+:6:11 note: in block
           $B3: {  # case
           ^^^
 
@@ -2191,7 +2190,7 @@
             exit_if  # if_1
             ^^^^^^^
 
-:6:11 note: In block
+:6:11 note: in block
           $B3: {  # body
           ^^^
 
@@ -2250,7 +2249,7 @@
         exit_switch  # undef
         ^^^^^^^^^^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # case
       ^^^
 
@@ -2291,7 +2290,7 @@
         exit_switch 1i  # switch_1
         ^^^^^^^^^^^^^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # case
       ^^^
 
@@ -2335,7 +2334,7 @@
         exit_switch 1i, 2.0f, 3i  # switch_1
         ^^^^^^^^^^^^^^^^^^^^^^^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # case
       ^^^
 
@@ -2397,7 +2396,7 @@
         exit_switch 1i, 2i  # switch_1
                         ^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # case
       ^^^
 
@@ -2441,7 +2440,7 @@
         exit_switch  # switch_1
         ^^^^^^^^^^^
 
-:9:7 note: In block
+:9:7 note: in block
       $B3: {  # true
       ^^^
 
@@ -2522,7 +2521,7 @@
             exit_switch  # switch_1
             ^^^^^^^^^^^
 
-:6:11 note: In block
+:6:11 note: in block
           $B3: {  # case
           ^^^
 
@@ -2573,7 +2572,7 @@
             exit_switch  # switch_1
             ^^^^^^^^^^^
 
-:6:11 note: In block
+:6:11 note: in block
           $B3: {  # body
           ^^^
 
@@ -2630,7 +2629,7 @@
         exit_loop  # undef
         ^^^^^^^^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # body
       ^^^
 
@@ -2673,7 +2672,7 @@
         exit_loop 1i  # loop_1
         ^^^^^^^^^^^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # body
       ^^^
 
@@ -2720,7 +2719,7 @@
         exit_loop 1i, 2.0f, 3i  # loop_1
         ^^^^^^^^^^^^^^^^^^^^^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # body
       ^^^
 
@@ -2785,7 +2784,7 @@
         exit_loop 1i, 2i  # loop_1
                       ^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # body
       ^^^
 
@@ -2831,7 +2830,7 @@
         exit_loop  # loop_1
         ^^^^^^^^^
 
-:12:7 note: In block
+:12:7 note: in block
       $B4: {  # true
       ^^^
 
@@ -2914,7 +2913,7 @@
             exit_loop  # loop_1
             ^^^^^^^^^
 
-:6:11 note: In block
+:6:11 note: in block
           $B4: {  # case
           ^^^
 
@@ -2969,7 +2968,7 @@
             exit_loop  # loop_1
             ^^^^^^^^^
 
-:6:11 note: In block
+:6:11 note: in block
           $B4: {  # body
           ^^^
 
@@ -3019,7 +3018,7 @@
         exit_loop  # loop_1
         ^^^^^^^^^
 
-:7:7 note: In block
+:7:7 note: in block
       $B3: {  # continuing
       ^^^
 
@@ -3065,7 +3064,7 @@
             exit_loop  # loop_1
             ^^^^^^^^^
 
-:9:11 note: In block
+:9:11 note: in block
           $B4: {  # true
           ^^^
 
@@ -3117,7 +3116,7 @@
         exit_loop  # loop_1
         ^^^^^^^^^
 
-:4:7 note: In block
+:4:7 note: in block
       $B2: {  # initializer
       ^^^
 
@@ -3167,7 +3166,7 @@
             exit_loop  # loop_1
             ^^^^^^^^^
 
-:6:11 note: In block
+:6:11 note: in block
           $B5: {  # true
           ^^^
 
@@ -3230,7 +3229,7 @@
     ret
     ^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3255,7 +3254,7 @@
     ret 42i
     ^^^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3280,7 +3279,7 @@
     ret
     ^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3306,7 +3305,7 @@
     ret 42.0f
     ^^^^^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3334,7 +3333,7 @@
     %2:i32 = load undef
                   ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3365,7 +3364,7 @@
     %3:f32 = load %l
                   ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3397,7 +3396,7 @@
     %3:f32 = load %2
                   ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3426,7 +3425,7 @@
     store undef, 42i
           ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3455,7 +3454,7 @@
     store %2, undef
               ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3486,7 +3485,7 @@
     store %l, 42u
               ^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3517,7 +3516,7 @@
     store %2, 42u
               ^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3549,7 +3548,7 @@
     undef = load_vector_element %2, 1i
     ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3579,7 +3578,7 @@
     %2:f32 = load_vector_element undef, 1i
                                  ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3609,7 +3608,7 @@
     %3:f32 = load_vector_element %2, undef
                                      ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3639,7 +3638,7 @@
     store_vector_element undef, 1i, 2i
                          ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3669,7 +3668,7 @@
     store_vector_element %2, undef, 2i
                              ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3677,7 +3676,7 @@
     store_vector_element %2, undef, 2i
                                     ^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3708,7 +3707,7 @@
     store_vector_element %2, 1i, undef
                                  ^^^^^
 
-:2:3 note: In block
+:2:3 note: in block
   $B1: {
   ^^^
 
@@ -3723,6 +3722,42 @@
 )");
 }
 
+TEST_F(IR_ValidatorTest, Scoping_UseBeforeDecl) {
+    auto* f = b.Function("my_func", ty.void_());
+
+    auto* y = b.Add<i32>(2_i, 3_i);
+    auto* x = b.Add<i32>(y, 1_i);
+
+    f->Block()->Append(x);
+    f->Block()->Append(y);
+    f->Block()->Append(b.Return(f));
+
+    auto res = ir::Validate(mod);
+    ASSERT_NE(res, Success);
+    EXPECT_EQ(res.Failure().reason.Str(),
+              R"(:3:18 error: binary: %3 is not in scope
+    %2:i32 = add %3, 1i
+                 ^^
+
+:2:3 note: in block
+  $B1: {
+  ^^^
+
+:4:5 note: %3 declared here
+    %3:i32 = add 2i, 3i
+    ^^^^^^
+
+note: # Disassembly
+%my_func = func():void {
+  $B1: {
+    %2:i32 = add %3, 1i
+    %3:i32 = add 2i, 3i
+    ret
+  }
+}
+)");
+}
+
 template <typename T>
 static const type::Type* TypeBuilder(type::Manager& m) {
     return m.Get<T>();
diff --git a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
index 067f38c..92b6cad 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer.cc
@@ -397,7 +397,7 @@
     auto* dst_type = TypeOf(call);
 
     if (!dst_type->is_integer_scalar_or_vector() && !dst_type->is_float_scalar_or_vector()) {
-        diagnostics_.AddError(diag::System::Writer, Source{})
+        diagnostics_.AddError(Source{})
             << "Unable to do bitcast to type " << dst_type->FriendlyName();
         return;
     }
@@ -1744,8 +1744,7 @@
         case wgsl::BuiltinFn::kUnpack4X8Unorm:
             return "unpackUnorm4x8";
         default:
-            diagnostics_.AddError(diag::System::Writer, Source{})
-                << "Unknown builtin method: " << builtin;
+            diagnostics_.AddError(Source{}) << "Unknown builtin method: " << builtin;
     }
 
     return "";
@@ -1923,7 +1922,7 @@
         [&](const ast::Let* let) { EmitProgramConstVariable(let); },
         [&](const ast::Override*) {
             // Override is removed with SubstituteOverride
-            diagnostics_.AddError(diag::System::Writer, Source{})
+            diagnostics_.AddError(Source{})
                 << "override-expressions should have been removed with the "
                    "SubstituteOverride transform";
         },
@@ -2192,7 +2191,7 @@
             out << "local_size_" << (i == 0 ? "x" : i == 1 ? "y" : "z") << " = ";
 
             if (!wgsize[i].has_value()) {
-                diagnostics_.AddError(diag::System::Writer, Source{})
+                diagnostics_.AddError(Source{})
                     << "override-expressions should have been removed with the SubstituteOverride "
                        "transform";
                 return;
@@ -2295,8 +2294,7 @@
 
             auto count = a->ConstantCount();
             if (!count) {
-                diagnostics_.AddError(diag::System::Writer, Source{})
-                    << core::type::Array::kErrExpectedConstantCount;
+                diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount;
                 return;
             }
 
@@ -2347,8 +2345,7 @@
                     return;
                 }
             }
-            diagnostics_.AddError(diag::System::Writer, Source{})
-                << "unknown integer literal suffix type";
+            diagnostics_.AddError(Source{}) << "unknown integer literal suffix type";
         },  //
         TINT_ICE_ON_NO_MATCH);
 }
@@ -2400,8 +2397,7 @@
 
         auto count = arr->ConstantCount();
         if (!count) {
-            diagnostics_.AddError(diag::System::Writer, Source{})
-                << core::type::Array::kErrExpectedConstantCount;
+            diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount;
             return;
         }
 
@@ -2412,7 +2408,7 @@
             EmitZeroValue(out, arr->ElemType());
         }
     } else {
-        diagnostics_.AddError(diag::System::Writer, Source{})
+        diagnostics_.AddError(Source{})
             << "Invalid type for zero emission: " << type->FriendlyName();
     }
 }
@@ -2688,8 +2684,7 @@
             } else {
                 auto count = arr->ConstantCount();
                 if (!count) {
-                    diagnostics_.AddError(diag::System::Writer, Source{})
-                        << core::type::Array::kErrExpectedConstantCount;
+                    diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount;
                     return;
                 }
                 sizes.push_back(count.value());
@@ -2844,7 +2839,7 @@
     } else if (type->Is<core::type::Void>()) {
         out << "void";
     } else {
-        diagnostics_.AddError(diag::System::Writer, Source{}) << "unknown type in EmitType";
+        diagnostics_.AddError(Source{}) << "unknown type in EmitType";
     }
 }
 
diff --git a/src/tint/lang/glsl/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/glsl/writer/ast_printer/ast_printer_test.cc
index 4c8c40c..125afe8 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer_test.cc
@@ -35,7 +35,7 @@
 using GlslASTPrinterTest = TestHelper;
 
 TEST_F(GlslASTPrinterTest, InvalidProgram) {
-    Diagnostics().AddError(diag::System::Writer, Source{}) << "make the program invalid";
+    Diagnostics().AddError(Source{}) << "make the program invalid";
     ASSERT_FALSE(IsValid());
     auto program = resolver::Resolve(*this);
     ASSERT_FALSE(program.IsValid());
diff --git a/src/tint/lang/glsl/writer/ast_raise/combine_samplers.cc b/src/tint/lang/glsl/writer/ast_raise/combine_samplers.cc
index 9b09f57..fcaef5e 100644
--- a/src/tint/lang/glsl/writer/ast_raise/combine_samplers.cc
+++ b/src/tint/lang/glsl/writer/ast_raise/combine_samplers.cc
@@ -441,8 +441,7 @@
     auto* binding_info = inputs.Get<BindingInfo>();
     if (!binding_info) {
         ProgramBuilder b;
-        b.Diagnostics().AddError(diag::System::Transform, Source{})
-            << "missing transform data for " << TypeInfo().name;
+        b.Diagnostics().AddError(Source{}) << "missing transform data for " << TypeInfo().name;
         return resolver::Resolve(b);
     }
 
diff --git a/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc b/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc
index 89cebe1..d0e8c71 100644
--- a/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc
+++ b/src/tint/lang/glsl/writer/ast_raise/texture_builtins_from_uniform.cc
@@ -74,7 +74,7 @@
     ApplyResult Run() {
         auto* cfg = inputs.Get<Config>();
         if (cfg == nullptr) {
-            b.Diagnostics().AddError(diag::System::Transform, Source{})
+            b.Diagnostics().AddError(Source{})
                 << "missing transform data for "
                 << tint::TypeInfo::Of<TextureBuiltinsFromUniform>().name;
             return resolver::Resolve(b);
diff --git a/src/tint/lang/hlsl/validate/BUILD.bazel b/src/tint/lang/hlsl/validate/BUILD.bazel
index 512dfd0..41ec67c 100644
--- a/src/tint/lang/hlsl/validate/BUILD.bazel
+++ b/src/tint/lang/hlsl/validate/BUILD.bazel
@@ -56,7 +56,13 @@
     "//src/tint/utils/rtti",
     "//src/tint/utils/text",
     "//src/tint/utils/traits",
-  ],
+  ] + select({
+    ":tint_build_hlsl_writer": [
+      
+      
+    ],
+    "//conditions:default": [],
+  }),
   copts = COPTS,
   visibility = ["//visibility:public"],
 )
diff --git a/src/tint/lang/hlsl/validate/BUILD.cmake b/src/tint/lang/hlsl/validate/BUILD.cmake
index a16018e..272eae6 100644
--- a/src/tint/lang/hlsl/validate/BUILD.cmake
+++ b/src/tint/lang/hlsl/validate/BUILD.cmake
@@ -59,4 +59,11 @@
   tint_utils_traits
 )
 
+if(TINT_BUILD_HLSL_WRITER)
+  tint_target_add_external_dependencies(tint_lang_hlsl_validate lib
+    "dl"
+    "dxc-include"
+  )
+endif(TINT_BUILD_HLSL_WRITER)
+
 endif(TINT_BUILD_HLSL_WRITER)
\ No newline at end of file
diff --git a/src/tint/lang/hlsl/validate/BUILD.gn b/src/tint/lang/hlsl/validate/BUILD.gn
index 44d66c5..b22d7b8 100644
--- a/src/tint/lang/hlsl/validate/BUILD.gn
+++ b/src/tint/lang/hlsl/validate/BUILD.gn
@@ -56,5 +56,12 @@
       "${tint_src_dir}/utils/text",
       "${tint_src_dir}/utils/traits",
     ]
+
+    if (tint_build_hlsl_writer) {
+      deps += [
+        "${tint_src_dir}:dl",
+        "${tint_src_dir}:dxc-include",
+      ]
+    }
   }
 }
diff --git a/src/tint/lang/hlsl/validate/validate.cc b/src/tint/lang/hlsl/validate/validate.cc
index cfca843..027756f 100644
--- a/src/tint/lang/hlsl/validate/validate.cc
+++ b/src/tint/lang/hlsl/validate/validate.cc
@@ -31,17 +31,32 @@
 
 #include "src/tint/utils/command/command.h"
 #include "src/tint/utils/file/tmpfile.h"
+#include "src/tint/utils/macros/defer.h"
 #include "src/tint/utils/text/string.h"
 
 #ifdef _WIN32
 #include <Windows.h>
+#include <atlbase.h>
 #include <d3dcommon.h>
 #include <d3dcompiler.h>
-
 #include <wrl.h>
-using Microsoft::WRL::ComPtr;
+#else
+#include <dlfcn.h>
 #endif  // _WIN32
 
+// dxc headers
+TINT_BEGIN_DISABLE_ALL_WARNINGS();
+#ifdef __clang__
+// # Use UUID emulation with clang to avoid compiling with ms-extensions
+#define __EMULATE_UUID
+#endif
+#include "dxc/dxcapi.h"
+TINT_END_DISABLE_ALL_WARNINGS();
+
+// Disable warnings about old-style casts which result from using
+// the SUCCEEDED and FAILED macros that C-style cast to HRESULT.
+TINT_DISABLE_WARNING_OLD_STYLE_CAST
+
 namespace tint::hlsl::validate {
 
 Result ValidateUsingDXC(const std::string& dxc_path,
@@ -51,9 +66,8 @@
                         uint32_t hlsl_shader_model) {
     Result result;
 
-    auto dxc = tint::Command(dxc_path);
-    if (!dxc.Found()) {
-        result.output = "DXC not found at '" + std::string(dxc_path) + "'";
+    if (entry_points.empty()) {
+        result.output = "No entrypoint found";
         result.failed = true;
         return result;
     }
@@ -70,64 +84,140 @@
         result.failed = true;
         return result;
     }
-    std::string shader_model_version =
-        std::to_string(hlsl_shader_model / 10) + "_" + std::to_string(hlsl_shader_model % 10);
 
-    tint::TmpFile file;
-    file << source;
+#define CHECK_HR(hr, error_msg)        \
+    do {                               \
+        if (FAILED(hr)) {              \
+            result.output = error_msg; \
+            result.failed = true;      \
+            return result;             \
+        }                              \
+    } while (false)
+
+    HRESULT hr;
+
+    // Load the dll and get the DxcCreateInstance function
+    using PFN_DXC_CREATE_INSTANCE =
+        HRESULT(__stdcall*)(REFCLSID rclsid, REFIID riid, LPVOID * ppCompiler);
+    PFN_DXC_CREATE_INSTANCE dxc_create_instance = nullptr;
+#ifdef _WIN32
+    HMODULE dxcLib = LoadLibraryA(dxc_path.c_str());
+    if (dxcLib == nullptr) {
+        result.output = "Failed to load dxc: " + dxc_path;
+        result.failed = true;
+        return result;
+    }
+    // Avoid ASAN false positives when unloading DLL: https://github.com/google/sanitizers/issues/89
+#if !defined(TINT_ASAN_ENABLED)
+    TINT_DEFER({ FreeLibrary(dxcLib); });
+#endif
+
+    dxc_create_instance =
+        reinterpret_cast<PFN_DXC_CREATE_INSTANCE>(GetProcAddress(dxcLib, "DxcCreateInstance"));
+#else
+    void* dxcLib = dlopen(dxc_path.c_str(), RTLD_LAZY | RTLD_GLOBAL);
+    if (dxcLib == nullptr) {
+        result.output = "Failed to load dxc: " + dxc_path;
+        result.failed = true;
+        return result;
+    }
+    // Avoid ASAN false positives when unloading DLL: https://github.com/google/sanitizers/issues/89
+#if !defined(TINT_ASAN_ENABLED)
+    TINT_DEFER({ dlclose(dxcLib); });
+#endif
+
+    dxc_create_instance =
+        reinterpret_cast<PFN_DXC_CREATE_INSTANCE>(dlsym(dxcLib, "DxcCreateInstance"));
+#endif
+    if (dxc_create_instance == nullptr) {
+        result.output = "GetProcAccess failed";
+        result.failed = true;
+        return result;
+    }
+
+    CComPtr<IDxcCompiler3> dxc_compiler;
+    hr = dxc_create_instance(CLSID_DxcCompiler, IID_PPV_ARGS(&dxc_compiler));
+    CHECK_HR(hr, "DxcCreateInstance failed");
 
     for (auto ep : entry_points) {
-        const char* stage_prefix = "";
-
+        const wchar_t* stage_prefix = L"";
         switch (ep.second) {
             case ast::PipelineStage::kNone:
                 result.output = "Invalid PipelineStage";
                 result.failed = true;
                 return result;
             case ast::PipelineStage::kVertex:
-                stage_prefix = "vs";
+                stage_prefix = L"vs";
                 break;
             case ast::PipelineStage::kFragment:
-                stage_prefix = "ps";
+                stage_prefix = L"ps";
                 break;
             case ast::PipelineStage::kCompute:
-                stage_prefix = "cs";
+                stage_prefix = L"cs";
                 break;
         }
 
         // Match Dawn's compile flags
         // See dawn\src\dawn_native\d3d12\RenderPipelineD3D12.cpp
         // and dawn_native\d3d\ShaderUtils.cpp (GetDXCArguments)
-        auto res =
-            dxc("-T " + std::string(stage_prefix) + "_" + shader_model_version,  // Profile
-                "-HV 2018",                                                      // Use HLSL 2018
-                "-E " + ep.first,                                                // Entry point
-                "/Zpr",  // D3DCOMPILE_PACK_MATRIX_ROW_MAJOR
-                "/Gis",  // D3DCOMPILE_IEEE_STRICTNESS
-                require_16bit_types ? "-enable-16bit-types" : "",  // Enable 16-bit if required
-                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);
+        std::wstring shader_model_version = std::to_wstring(hlsl_shader_model / 10) + L"_" +
+                                            std::to_wstring(hlsl_shader_model % 10);
+        std::wstring profile = std::wstring(stage_prefix) + L"_" + shader_model_version;
+        std::wstring entry_point = std::wstring(ep.first.begin(), ep.first.end());
+        std::vector<const wchar_t*> args{
+            L"-T",                                              // Profile
+            profile.c_str(),                                    //
+            L"-HV 2018",                                        // Use HLSL 2018
+            L"-E",                                              // Entry point
+            entry_point.c_str(),                                //
+            L"/Zpr",                                            // D3DCOMPILE_PACK_MATRIX_ROW_MAJOR
+            L"/Gis",                                            // D3DCOMPILE_IEEE_STRICTNESS
+            require_16bit_types ? L"-enable-16bit-types" : L""  // Enable 16-bit if required
+        };
 
-        // Remove the temporary file name from the output to keep output deterministic
-        result.output = tint::ReplaceAll(result.output, file.Path(), "shader.hlsl");
-    }
+        DxcBuffer source_buffer;
+        source_buffer.Ptr = source.c_str();
+        source_buffer.Size = source.length();
+        source_buffer.Encoding = DXC_CP_UTF8;
+        CComPtr<IDxcResult> compile_result;
+        hr = dxc_compiler->Compile(&source_buffer, args.data(), static_cast<UINT32>(args.size()),
+                                   nullptr, IID_PPV_ARGS(&compile_result));
+        CHECK_HR(hr, "Compile call failed");
 
-    if (entry_points.empty()) {
-        result.output = "No entrypoint found";
-        result.failed = true;
-        return result;
+        HRESULT compile_status;
+        hr = compile_result->GetStatus(&compile_status);
+        CHECK_HR(hr, "GetStatus call failed");
+
+        if (FAILED(compile_status)) {
+            CComPtr<IDxcBlobEncoding> errors;
+            hr = compile_result->GetErrorBuffer(&errors);
+            CHECK_HR(hr, "GetErrorBuffer call failed");
+            result.output = static_cast<char*>(errors->GetBufferPointer());
+            result.failed = true;
+            return result;
+        }
+
+        // Compilation succeeded, get compiled shader blob and disassamble it
+        CComPtr<IDxcBlob> compiled_shader;
+        hr = compile_result->GetResult(&compiled_shader);
+        CHECK_HR(hr, "GetResult call failed");
+
+        DxcBuffer compiled_shader_buffer;
+        compiled_shader_buffer.Ptr = compiled_shader->GetBufferPointer();
+        compiled_shader_buffer.Size = compiled_shader->GetBufferSize();
+        compiled_shader_buffer.Encoding = DXC_CP_UTF8;
+        CComPtr<IDxcResult> dis_result;
+        hr = dxc_compiler->Disassemble(&compiled_shader_buffer, IID_PPV_ARGS(&dis_result));
+        CHECK_HR(hr, "Disassemble call failed");
+
+        CComPtr<IDxcBlobEncoding> disassembly;
+        if (dis_result && dis_result->HasOutput(DXC_OUT_DISASSEMBLY) &&
+            SUCCEEDED(
+                dis_result->GetOutput(DXC_OUT_DISASSEMBLY, IID_PPV_ARGS(&disassembly), nullptr))) {
+            result.output = static_cast<char*>(disassembly->GetBufferPointer());
+        } else {
+            result.output = "Failed to disassemble shader";
+        }
     }
 
     return result;
@@ -139,6 +229,12 @@
                         const EntryPointList& entry_points) {
     Result result;
 
+    if (entry_points.empty()) {
+        result.output = "No entrypoint found";
+        result.failed = true;
+        return 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 UsingFXC
     // are short-lived.
@@ -188,8 +284,8 @@
         UINT compileFlags = D3DCOMPILE_OPTIMIZATION_LEVEL0 | D3DCOMPILE_PACK_MATRIX_ROW_MAJOR |
                             D3DCOMPILE_IEEE_STRICTNESS;
 
-        ComPtr<ID3DBlob> compiledShader;
-        ComPtr<ID3DBlob> errors;
+        CComPtr<ID3DBlob> compiledShader;
+        CComPtr<ID3DBlob> errors;
         HRESULT res = d3dCompile(source.c_str(),    // pSrcData
                                  source.length(),   // SrcDataSize
                                  nullptr,           // pSourceName
@@ -206,11 +302,11 @@
             result.failed = true;
             return result;
         } else {
-            ComPtr<ID3DBlob> disassembly;
+            CComPtr<ID3DBlob> disassembly;
             res = d3dDisassemble(compiledShader->GetBufferPointer(),
                                  compiledShader->GetBufferSize(), 0, "", &disassembly);
             if (FAILED(res)) {
-                result.output = "failed to disassemble shader";
+                result.output = "Failed to disassemble shader";
             } else {
                 result.output = static_cast<char*>(disassembly->GetBufferPointer());
             }
@@ -219,12 +315,6 @@
 
     FreeLibrary(fxcLib);
 
-    if (entry_points.empty()) {
-        result.output = "No entrypoint found";
-        result.failed = true;
-        return result;
-    }
-
     return result;
 }
 #endif  // _WIN32
diff --git a/src/tint/lang/hlsl/validate/validate.h b/src/tint/lang/hlsl/validate/validate.h
index 8efb4fb..67ce25e 100644
--- a/src/tint/lang/hlsl/validate/validate.h
+++ b/src/tint/lang/hlsl/validate/validate.h
@@ -46,6 +46,16 @@
 /// Name of the FXC compiler DLL
 static constexpr const char kFxcDLLName[] = "d3dcompiler_47.dll";
 
+#if TINT_BUILD_IS_WIN
+static constexpr const char* kDxcDLLName = "dxcompiler.dll";
+#elif TINT_BUILD_IS_LINUX
+static constexpr const char* kDxcDLLName = "libdxcompiler.so";
+#elif TINT_BUILD_IS_MAC
+static constexpr const char* kDxcDLLName = "libdxcompiler.dylib";
+#else
+static constexpr const char* kDxcDLLName = "Invalid";
+#endif
+
 /// The return structure of Validate()
 struct Result {
     /// True if validation passed
diff --git a/src/tint/lang/hlsl/writer/BUILD.cmake b/src/tint/lang/hlsl/writer/BUILD.cmake
index a40f368..67af1a7 100644
--- a/src/tint/lang/hlsl/writer/BUILD.cmake
+++ b/src/tint/lang/hlsl/writer/BUILD.cmake
@@ -159,6 +159,7 @@
   tint_lang_wgsl_program
   tint_lang_wgsl_sem
   tint_utils_bytes
+  tint_utils_command
   tint_utils_containers
   tint_utils_diagnostic
   tint_utils_ice
@@ -176,6 +177,7 @@
 
 if(TINT_BUILD_HLSL_WRITER)
   tint_target_add_dependencies(tint_lang_hlsl_writer_fuzz fuzz
+    tint_lang_hlsl_validate
     tint_lang_hlsl_writer
   )
 endif(TINT_BUILD_HLSL_WRITER)
diff --git a/src/tint/lang/hlsl/writer/BUILD.gn b/src/tint/lang/hlsl/writer/BUILD.gn
index 4c8f704..2111f1c 100644
--- a/src/tint/lang/hlsl/writer/BUILD.gn
+++ b/src/tint/lang/hlsl/writer/BUILD.gn
@@ -140,6 +140,7 @@
       "${tint_src_dir}/lang/wgsl/program",
       "${tint_src_dir}/lang/wgsl/sem",
       "${tint_src_dir}/utils/bytes",
+      "${tint_src_dir}/utils/command",
       "${tint_src_dir}/utils/containers",
       "${tint_src_dir}/utils/diagnostic",
       "${tint_src_dir}/utils/ice",
@@ -156,7 +157,10 @@
     ]
 
     if (tint_build_hlsl_writer) {
-      deps += [ "${tint_src_dir}/lang/hlsl/writer" ]
+      deps += [
+        "${tint_src_dir}/lang/hlsl/validate",
+        "${tint_src_dir}/lang/hlsl/writer",
+      ]
     }
 
     if (tint_build_wgsl_reader) {
diff --git a/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
index 13b1fd6..35a7140 100644
--- a/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/hlsl/writer/ast_printer/ast_printer.cc
@@ -709,7 +709,7 @@
     auto* dst_el_type = dst_type->DeepestElement();
 
     if (!dst_el_type->is_integer_scalar() && !dst_el_type->is_float_scalar()) {
-        diagnostics_.AddError(diag::System::Writer, Source{})
+        diagnostics_.AddError(Source{})
             << "Unable to do bitcast to type " << dst_el_type->FriendlyName();
         return false;
     }
@@ -3122,8 +3122,7 @@
         case wgsl::BuiltinFn::kSubgroupBroadcast:
             return "WaveReadLaneAt";
         default:
-            diagnostics_.AddError(diag::System::Writer, Source{})
-                << "Unknown builtin method: " << builtin->str();
+            diagnostics_.AddError(Source{}) << "Unknown builtin method: " << builtin->str();
     }
 
     return "";
@@ -3394,7 +3393,7 @@
                 case core::AddressSpace::kWorkgroup:
                     return EmitWorkgroupVariable(sem);
                 case core::AddressSpace::kPushConstant:
-                    diagnostics_.AddError(diag::System::Writer, Source{})
+                    diagnostics_.AddError(Source{})
                         << "unhandled address space " << sem->AddressSpace();
                     return false;
                 default: {
@@ -3405,7 +3404,7 @@
         },
         [&](const ast::Override*) {
             // Override is removed with SubstituteOverride
-            diagnostics_.AddError(diag::System::Writer, Source{})
+            diagnostics_.AddError(Source{})
                 << "override-expressions should have been removed with the SubstituteOverride "
                    "transform";
             return false;
@@ -3630,7 +3629,7 @@
                     out << ", ";
                 }
                 if (!wgsize[i].has_value()) {
-                    diagnostics_.AddError(diag::System::Writer, Source{})
+                    diagnostics_.AddError(Source{})
                         << "override-expressions should have been removed with the "
                            "SubstituteOverride transform";
                     return false;
@@ -3784,8 +3783,7 @@
 
             auto count = a->ConstantCount();
             if (!count) {
-                diagnostics_.AddError(diag::System::Writer, Source{})
-                    << core::type::Array::kErrExpectedConstantCount;
+                diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount;
                 return false;
             }
 
@@ -3880,8 +3878,7 @@
                     out << "u";
                     return true;
             }
-            diagnostics_.AddError(diag::System::Writer, Source{})
-                << "unknown integer literal suffix type";
+            diagnostics_.AddError(Source{}) << "unknown integer literal suffix type";
             return false;
         },  //
         TINT_ICE_ON_NO_MATCH);
@@ -4348,8 +4345,7 @@
                 }
                 const auto count = arr->ConstantCount();
                 if (!count) {
-                    diagnostics_.AddError(diag::System::Writer, Source{})
-                        << core::type::Array::kErrExpectedConstantCount;
+                    diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount;
                     return false;
                 }
 
@@ -4588,7 +4584,7 @@
             if (auto builtin = attributes.builtin) {
                 auto name = builtin_to_attribute(builtin.value());
                 if (name.empty()) {
-                    diagnostics_.AddError(diag::System::Writer, Source{}) << "unsupported builtin";
+                    diagnostics_.AddError(Source{}) << "unsupported builtin";
                     return false;
                 }
                 post += " : " + name;
@@ -4596,8 +4592,7 @@
             if (auto interpolation = attributes.interpolation) {
                 auto mod = interpolation_to_modifiers(interpolation->type, interpolation->sampling);
                 if (mod.empty()) {
-                    diagnostics_.AddError(diag::System::Writer, Source{})
-                        << "unsupported interpolation";
+                    diagnostics_.AddError(Source{}) << "unsupported interpolation";
                     return false;
                 }
                 pre += mod;
diff --git a/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc
index 8348b54..1a99d1f 100644
--- a/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc
@@ -35,7 +35,7 @@
 using HlslASTPrinterTest = TestHelper;
 
 TEST_F(HlslASTPrinterTest, InvalidProgram) {
-    Diagnostics().AddError(diag::System::Writer, Source{}) << "make the program invalid";
+    Diagnostics().AddError(Source{}) << "make the program invalid";
     ASSERT_FALSE(IsValid());
     auto program = resolver::Resolve(*this);
     ASSERT_FALSE(program.IsValid());
diff --git a/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.cc b/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.cc
index f266d83..bee8ba5 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/num_workgroups_from_uniform.cc
@@ -90,8 +90,7 @@
 
     auto* cfg = inputs.Get<Config>();
     if (cfg == nullptr) {
-        b.Diagnostics().AddError(diag::System::Transform, Source{})
-            << "missing transform data for " << TypeInfo().name;
+        b.Diagnostics().AddError(Source{}) << "missing transform data for " << TypeInfo().name;
         return resolver::Resolve(b);
     }
 
diff --git a/src/tint/lang/hlsl/writer/ast_raise/pixel_local.cc b/src/tint/lang/hlsl/writer/ast_raise/pixel_local.cc
index fd59677..d193740 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/pixel_local.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/pixel_local.cc
@@ -469,7 +469,7 @@
     uint32_t ROVRegisterIndex(uint32_t field_index) {
         auto idx = cfg.pls_member_to_rov_reg.Get(field_index);
         if (TINT_UNLIKELY(!idx)) {
-            b.Diagnostics().AddError(diag::System::Transform, Source{})
+            b.Diagnostics().AddError(Source{})
                 << "PixelLocal::Config::attachments missing entry for field " << field_index;
             return 0;
         }
@@ -501,8 +501,7 @@
     auto* cfg = inputs.Get<Config>();
     if (!cfg) {
         ProgramBuilder b;
-        b.Diagnostics().AddError(diag::System::Transform, Source{})
-            << "missing transform data for " << TypeInfo().name;
+        b.Diagnostics().AddError(Source{}) << "missing transform data for " << TypeInfo().name;
         return resolver::Resolve(b);
     }
 
diff --git a/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.cc b/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.cc
index 7028093..07dd208 100644
--- a/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.cc
+++ b/src/tint/lang/hlsl/writer/ast_raise/truncate_interstage_variables.cc
@@ -70,7 +70,7 @@
 
     const auto* data = config.Get<Config>();
     if (data == nullptr) {
-        b.Diagnostics().AddError(diag::System::Transform, Source{})
+        b.Diagnostics().AddError(Source{})
             << "missing transform data for "
             << tint::TypeInfo::Of<TruncateInterstageVariables>().name;
         return resolver::Resolve(b);
diff --git a/src/tint/lang/hlsl/writer/common/option_helpers.cc b/src/tint/lang/hlsl/writer/common/option_helpers.cc
index 416569d..ec26be9 100644
--- a/src/tint/lang/hlsl/writer/common/option_helpers.cc
+++ b/src/tint/lang/hlsl/writer/common/option_helpers.cc
@@ -54,8 +54,7 @@
                                                          const binding::BindingInfo& dst) -> bool {
         if (auto binding = seen_wgsl_bindings.Get(src)) {
             if (*binding != dst) {
-                diagnostics.AddError(diag::System::Writer, Source{})
-                    << "found duplicate WGSL binding point: " << src;
+                diagnostics.AddError(Source{}) << "found duplicate WGSL binding point: " << src;
                 return true;
             }
         }
@@ -67,7 +66,7 @@
                                     const tint::BindingPoint& dst) -> bool {
         if (auto binding = map.Get(src)) {
             if (*binding != dst) {
-                diagnostics.AddError(diag::System::Writer, Source{})
+                diagnostics.AddError(Source{})
                     << "found duplicate MSL binding point: [binding: " << src.binding << "]";
                 return true;
             }
@@ -94,27 +93,27 @@
 
     // Storage and uniform are both [[buffer()]]
     if (!valid(seen_hlsl_buffer_bindings, options.bindings.uniform)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing uniform";
+        diagnostics.AddNote(Source{}) << "when processing uniform";
         return Failure{std::move(diagnostics)};
     }
     if (!valid(seen_hlsl_buffer_bindings, options.bindings.storage)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing storage";
+        diagnostics.AddNote(Source{}) << "when processing storage";
         return Failure{std::move(diagnostics)};
     }
 
     // Sampler is [[sampler()]]
     if (!valid(seen_hlsl_sampler_bindings, options.bindings.sampler)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing sampler";
+        diagnostics.AddNote(Source{}) << "when processing sampler";
         return Failure{std::move(diagnostics)};
     }
 
     // Texture and storage texture are [[texture()]]
     if (!valid(seen_hlsl_texture_bindings, options.bindings.texture)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing texture";
+        diagnostics.AddNote(Source{}) << "when processing texture";
         return Failure{std::move(diagnostics)};
     }
     if (!valid(seen_hlsl_texture_bindings, options.bindings.storage_texture)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing storage_texture";
+        diagnostics.AddNote(Source{}) << "when processing storage_texture";
         return Failure{std::move(diagnostics)};
     }
 
@@ -126,26 +125,22 @@
 
         // Validate with the actual source regardless of what the remapper will do
         if (wgsl_seen(src_binding, plane0)) {
-            diagnostics.AddNote(diag::System::Writer, Source{})
-                << "when processing external_texture";
+            diagnostics.AddNote(Source{}) << "when processing external_texture";
             return Failure{std::move(diagnostics)};
         }
 
         // Plane0 & Plane1 are [[texture()]]
         if (hlsl_seen(seen_hlsl_texture_bindings, plane0, src_binding)) {
-            diagnostics.AddNote(diag::System::Writer, Source{})
-                << "when processing external_texture";
+            diagnostics.AddNote(Source{}) << "when processing external_texture";
             return Failure{std::move(diagnostics)};
         }
         if (hlsl_seen(seen_hlsl_texture_bindings, plane1, src_binding)) {
-            diagnostics.AddNote(diag::System::Writer, Source{})
-                << "when processing external_texture";
+            diagnostics.AddNote(Source{}) << "when processing external_texture";
             return Failure{std::move(diagnostics)};
         }
         // Metadata is [[buffer()]]
         if (hlsl_seen(seen_hlsl_buffer_bindings, metadata, src_binding)) {
-            diagnostics.AddNote(diag::System::Writer, Source{})
-                << "when processing external_texture";
+            diagnostics.AddNote(Source{}) << "when processing external_texture";
             return Failure{std::move(diagnostics)};
         }
     }
diff --git a/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc b/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc
index a2865f9..12497e6 100644
--- a/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc
+++ b/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc
@@ -27,19 +27,58 @@
 
 // GEN_BUILD:CONDITION(tint_build_wgsl_reader)
 
+#include <string>
+#include <unordered_map>
+
 #include "src/tint/cmd/fuzz/wgsl/fuzz.h"
+#include "src/tint/lang/hlsl/validate/validate.h"
 #include "src/tint/lang/hlsl/writer/writer.h"
 #include "src/tint/lang/wgsl/ast/module.h"
+#include "src/tint/utils/command/command.h"
 
 namespace tint::hlsl::writer {
 namespace {
 
-void ASTFuzzer(const tint::Program& program, Options options) {
+void ASTFuzzer(const tint::Program& program,
+               const fuzz::wgsl::Options& fuzz_options,
+               Options options) {
     if (program.AST().HasOverrides()) {
         return;
     }
 
-    [[maybe_unused]] auto res = tint::hlsl::writer::Generate(program, options);
+    auto res = tint::hlsl::writer::Generate(program, options);
+    if (res == Success) {
+        const char* dxc_path = validate::kDxcDLLName;
+        bool must_validate = false;
+        if (!fuzz_options.dxc.empty()) {
+            must_validate = true;
+            dxc_path = fuzz_options.dxc.c_str();
+        }
+
+        auto dxc = tint::Command::LookPath(dxc_path);
+        if (dxc.Found()) {
+            uint32_t hlsl_shader_model = 60;
+            bool require_16bit_types = false;
+            auto enable_list = program.AST().Enables();
+            for (auto* enable : enable_list) {
+                if (enable->HasExtension(tint::wgsl::Extension::kF16)) {
+                    hlsl_shader_model = 62;
+                    require_16bit_types = true;
+                    break;
+                }
+            }
+
+            auto validate_res = validate::ValidateUsingDXC(dxc.Path(), res->hlsl, res->entry_points,
+                                                           require_16bit_types, hlsl_shader_model);
+
+            if (must_validate && validate_res.failed) {
+                TINT_ICE() << "DXC was expected to succeed, but failed: " << validate_res.output;
+            }
+
+        } else if (must_validate) {
+            TINT_ICE() << "DXC path was explicitly specified, but was not found: " << dxc_path;
+        }
+    }
 }
 
 }  // namespace
diff --git a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
index bb583cd..0ba01d6 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
@@ -314,7 +314,7 @@
             },
             [&](const ast::Override*) {
                 // Override is removed with SubstituteOverride
-                diagnostics_.AddError(diag::System::Writer, Source{})
+                diagnostics_.AddError(Source{})
                     << "override-expressions should have been removed with the "
                        "SubstituteOverride transform.";
                 return false;
@@ -373,8 +373,7 @@
             return false;
         }
     } else {
-        diagnostics_.AddError(diag::System::Writer, Source{})
-            << "unknown alias type: " << ty->FriendlyName();
+        diagnostics_.AddError(Source{}) << "unknown alias type: " << ty->FriendlyName();
         return false;
     }
 
@@ -1073,8 +1072,7 @@
             std::vector<const char*> dims;
             switch (texture_type->dim()) {
                 case core::type::TextureDimension::kNone:
-                    diagnostics_.AddError(diag::System::Writer, Source{})
-                        << "texture dimension is kNone";
+                    diagnostics_.AddError(Source{}) << "texture dimension is kNone";
                     return false;
                 case core::type::TextureDimension::k1d:
                     dims = {"width"};
@@ -1273,7 +1271,7 @@
                 out << "gradientcube(";
                 break;
             default: {
-                diagnostics_.AddError(diag::System::Writer, Source{})
+                diagnostics_.AddError(Source{})
                     << "MSL does not support gradients for " << dim << " textures";
                 return false;
             }
@@ -1630,13 +1628,12 @@
             out += "unpack_unorm2x16_to_float";
             break;
         case wgsl::BuiltinFn::kArrayLength:
-            diagnostics_.AddError(diag::System::Writer, Source{})
+            diagnostics_.AddError(Source{})
                 << "Unable to translate builtin: " << builtin->Fn()
                 << "\nDid you forget to pass array_length_from_uniform generator options?";
             return "";
         default:
-            diagnostics_.AddError(diag::System::Writer, Source{})
-                << "Unknown import method: " << builtin->Fn();
+            diagnostics_.AddError(Source{}) << "Unknown import method: " << builtin->Fn();
             return "";
     }
     return out;
@@ -1811,8 +1808,7 @@
 
             auto count = a->ConstantCount();
             if (!count) {
-                diagnostics_.AddError(diag::System::Writer, Source{})
-                    << core::type::Array::kErrExpectedConstantCount;
+                diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount;
                 return false;
             }
 
@@ -1882,8 +1878,7 @@
                     return true;
                 }
             }
-            diagnostics_.AddError(diag::System::Writer, Source{})
-                << "unknown integer literal suffix type";
+            diagnostics_.AddError(Source{}) << "unknown integer literal suffix type";
             return false;
         },  //
         TINT_ICE_ON_NO_MATCH);
@@ -2078,8 +2073,7 @@
 
                         auto name = BuiltinToAttribute(builtin);
                         if (name.empty()) {
-                            diagnostics_.AddError(diag::System::Writer, Source{})
-                                << "unknown builtin";
+                            diagnostics_.AddError(Source{}) << "unknown builtin";
                             return false;
                         }
                         out << " [[" << name << "]]";
@@ -2536,8 +2530,7 @@
             } else {
                 auto count = arr->ConstantCount();
                 if (!count) {
-                    diagnostics_.AddError(diag::System::Writer, Source{})
-                        << core::type::Array::kErrExpectedConstantCount;
+                    diagnostics_.AddError(Source{}) << core::type::Array::kErrExpectedConstantCount;
                     return false;
                 }
 
@@ -2632,8 +2625,7 @@
                     out << "cube_array";
                     break;
                 default:
-                    diagnostics_.AddError(diag::System::Writer, Source{})
-                        << "Invalid texture dimensions";
+                    diagnostics_.AddError(Source{}) << "Invalid texture dimensions";
                     return false;
             }
             if (tex->IsAnyOf<core::type::MultisampledTexture,
@@ -2666,7 +2658,7 @@
                     } else if (storage->access() == core::Access::kWrite) {
                         out << ", access::write";
                     } else {
-                        diagnostics_.AddError(diag::System::Writer, Source{})
+                        diagnostics_.AddError(Source{})
                             << "Invalid access control for storage texture";
                         return false;
                     }
@@ -2808,7 +2800,7 @@
         if (auto builtin = attributes.builtin) {
             auto name = BuiltinToAttribute(builtin.value());
             if (name.empty()) {
-                diagnostics_.AddError(diag::System::Writer, Source{}) << "unknown builtin";
+                diagnostics_.AddError(Source{}) << "unknown builtin";
                 return false;
             }
             out << " [[" << name << "]]";
@@ -2850,8 +2842,7 @@
         if (auto interpolation = attributes.interpolation) {
             auto name = InterpolationToAttribute(interpolation->type, interpolation->sampling);
             if (name.empty()) {
-                diagnostics_.AddError(diag::System::Writer, Source{})
-                    << "unknown interpolation attribute";
+                diagnostics_.AddError(Source{}) << "unknown interpolation attribute";
                 return false;
             }
             out << " [[" << name << "]]";
diff --git a/src/tint/lang/msl/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/msl/writer/ast_printer/ast_printer_test.cc
index 5a1e8c0..4f60358 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer_test.cc
@@ -39,7 +39,7 @@
 using MslASTPrinterTest = TestHelper;
 
 TEST_F(MslASTPrinterTest, InvalidProgram) {
-    Diagnostics().AddError(diag::System::Writer, Source{}) << "make the program invalid";
+    Diagnostics().AddError(Source{}) << "make the program invalid";
     ASSERT_FALSE(IsValid());
     auto program = resolver::Resolve(*this);
     ASSERT_FALSE(program.IsValid());
diff --git a/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.cc b/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.cc
index 0084d30..370164a 100644
--- a/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.cc
+++ b/src/tint/lang/msl/writer/ast_raise/module_scope_var_to_entry_point_param.cc
@@ -244,7 +244,7 @@
             case core::AddressSpace::kWorkgroup:
                 break;
             case core::AddressSpace::kPushConstant: {
-                ctx.dst->Diagnostics().AddError(diag::System::Transform, Source{})
+                ctx.dst->Diagnostics().AddError(Source{})
                     << "unhandled module-scope address space (" << sc << ")";
                 break;
             }
diff --git a/src/tint/lang/msl/writer/ast_raise/pixel_local.cc b/src/tint/lang/msl/writer/ast_raise/pixel_local.cc
index c6cc55a..a9a79d8 100644
--- a/src/tint/lang/msl/writer/ast_raise/pixel_local.cc
+++ b/src/tint/lang/msl/writer/ast_raise/pixel_local.cc
@@ -257,7 +257,7 @@
     uint32_t AttachmentIndex(uint32_t field_index) {
         auto idx = cfg.attachments.Get(field_index);
         if (TINT_UNLIKELY(!idx)) {
-            b.Diagnostics().AddError(diag::System::Transform, Source{})
+            b.Diagnostics().AddError(Source{})
                 << "PixelLocal::Config::attachments missing entry for field " << field_index;
             return 0;
         }
@@ -275,8 +275,7 @@
     auto* cfg = inputs.Get<Config>();
     if (!cfg) {
         ProgramBuilder b;
-        b.Diagnostics().AddError(diag::System::Transform, Source{})
-            << "missing transform data for " << TypeInfo().name;
+        b.Diagnostics().AddError(Source{}) << "missing transform data for " << TypeInfo().name;
         return resolver::Resolve(b);
     }
 
diff --git a/src/tint/lang/msl/writer/common/option_helpers.cc b/src/tint/lang/msl/writer/common/option_helpers.cc
index ab4857b..ace6898 100644
--- a/src/tint/lang/msl/writer/common/option_helpers.cc
+++ b/src/tint/lang/msl/writer/common/option_helpers.cc
@@ -53,8 +53,7 @@
     auto wgsl_seen = [&diagnostics, &seen_wgsl_bindings](const tint::BindingPoint& src,
                                                          const binding::BindingInfo& dst) -> bool {
         if (auto binding = seen_wgsl_bindings.Add(src, dst); binding.value != dst) {
-            diagnostics.AddError(diag::System::Writer, Source{})
-                << "found duplicate WGSL binding point: " << src;
+            diagnostics.AddError(Source{}) << "found duplicate WGSL binding point: " << src;
             return true;
         }
         return false;
@@ -63,7 +62,7 @@
     auto msl_seen = [&diagnostics](InfoToPointMap& map, const binding::BindingInfo& src,
                                    const tint::BindingPoint& dst) -> bool {
         if (auto binding = map.Add(src, dst); binding.value != dst) {
-            diagnostics.AddError(diag::System::Writer, Source{})
+            diagnostics.AddError(Source{})
                 << "found duplicate MSL binding point: [binding: " << src.binding << "]";
             return true;
         }
@@ -88,27 +87,27 @@
 
     // Storage and uniform are both [[buffer()]]
     if (!valid(seen_msl_buffer_bindings, options.bindings.uniform)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing uniform";
+        diagnostics.AddNote(Source{}) << "when processing uniform";
         return Failure{std::move(diagnostics)};
     }
     if (!valid(seen_msl_buffer_bindings, options.bindings.storage)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing storage";
+        diagnostics.AddNote(Source{}) << "when processing storage";
         return Failure{std::move(diagnostics)};
     }
 
     // Sampler is [[sampler()]]
     if (!valid(seen_msl_sampler_bindings, options.bindings.sampler)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing sampler";
+        diagnostics.AddNote(Source{}) << "when processing sampler";
         return Failure{std::move(diagnostics)};
     }
 
     // Texture and storage texture are [[texture()]]
     if (!valid(seen_msl_texture_bindings, options.bindings.texture)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing texture";
+        diagnostics.AddNote(Source{}) << "when processing texture";
         return Failure{std::move(diagnostics)};
     }
     if (!valid(seen_msl_texture_bindings, options.bindings.storage_texture)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing storage_texture";
+        diagnostics.AddNote(Source{}) << "when processing storage_texture";
         return Failure{std::move(diagnostics)};
     }
 
@@ -120,26 +119,22 @@
 
         // Validate with the actual source regardless of what the remapper will do
         if (wgsl_seen(src_binding, plane0)) {
-            diagnostics.AddNote(diag::System::Writer, Source{})
-                << "when processing external_texture";
+            diagnostics.AddNote(Source{}) << "when processing external_texture";
             return Failure{std::move(diagnostics)};
         }
 
         // Plane0 & Plane1 are [[texture()]]
         if (msl_seen(seen_msl_texture_bindings, plane0, src_binding)) {
-            diagnostics.AddNote(diag::System::Writer, Source{})
-                << "when processing external_texture";
+            diagnostics.AddNote(Source{}) << "when processing external_texture";
             return Failure{std::move(diagnostics)};
         }
         if (msl_seen(seen_msl_texture_bindings, plane1, src_binding)) {
-            diagnostics.AddNote(diag::System::Writer, Source{})
-                << "when processing external_texture";
+            diagnostics.AddNote(Source{}) << "when processing external_texture";
             return Failure{std::move(diagnostics)};
         }
         // Metadata is [[buffer()]]
         if (msl_seen(seen_msl_buffer_bindings, metadata, src_binding)) {
-            diagnostics.AddNote(diag::System::Writer, Source{})
-                << "when processing external_texture";
+            diagnostics.AddNote(Source{}) << "when processing external_texture";
             return Failure{std::move(diagnostics)};
         }
     }
diff --git a/src/tint/lang/msl/writer/raise/builtin_polyfill.cc b/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
index 9788113..5887c3e 100644
--- a/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
@@ -76,13 +76,13 @@
             core::ir::Value* replacement = nullptr;
             switch (builtin->Func()) {
                 case core::BuiltinFn::kStorageBarrier:
-                    replacement = StorageBarrier(builtin);
+                    replacement = ThreadgroupBarrier(builtin, BarrierType::kDevice);
                     break;
                 case core::BuiltinFn::kWorkgroupBarrier:
-                    replacement = WorkgroupBarrier(builtin);
+                    replacement = ThreadgroupBarrier(builtin, BarrierType::kThreadGroup);
                     break;
                 case core::BuiltinFn::kTextureBarrier:
-                    replacement = TextureBarrier(builtin);
+                    replacement = ThreadgroupBarrier(builtin, BarrierType::kTexture);
                     break;
                 default:
                     break;
@@ -98,39 +98,13 @@
         }
     }
 
-    /// Handle a `workgroupBarrier()` builtin.
+    /// Replace a barrier builtin with the `threadgroupBarrier()` intrinsic.
     /// @param builtin the builtin call instruction
+    /// @param type the barrier type
     /// @returns the replacement value
-    core::ir::Value* WorkgroupBarrier(core::ir::CoreBuiltinCall* builtin) {
+    core::ir::Value* ThreadgroupBarrier(core::ir::CoreBuiltinCall* builtin, BarrierType type) {
         // Replace the builtin call with a call to the msl.threadgroup_barrier intrinsic.
-        auto args = Vector<core::ir::Value*, 4>{b.Constant(u32(BarrierType::kThreadGroup))};
-
-        auto* call = b.Call<msl::ir::BuiltinCall>(
-            builtin->Result(0)->Type(), msl::BuiltinFn::kThreadgroupBarrier, std::move(args));
-        call->InsertBefore(builtin);
-        return call->Result(0);
-    }
-
-    /// Handle a `storageBarrier()` builtin.
-    /// @param builtin the builtin call instruction
-    /// @returns the replacement value
-    core::ir::Value* StorageBarrier(core::ir::CoreBuiltinCall* builtin) {
-        // Replace the builtin call with a call to the msl.threadgroup_barrier intrinsic.
-        auto args = Vector<core::ir::Value*, 4>{b.Constant(u32(BarrierType::kDevice))};
-
-        auto* call = b.Call<msl::ir::BuiltinCall>(
-            builtin->Result(0)->Type(), msl::BuiltinFn::kThreadgroupBarrier, std::move(args));
-        call->InsertBefore(builtin);
-        return call->Result(0);
-    }
-
-    /// Handle a `textureBarrier()` builtin.
-    /// @param builtin the builtin call instruction
-    /// @returns the replacement value
-    core::ir::Value* TextureBarrier(core::ir::CoreBuiltinCall* builtin) {
-        // Replace the builtin call with a call to the msl.threadgroup_barrier intrinsic.
-        auto args = Vector<core::ir::Value*, 4>{b.Constant(u32(BarrierType::kTexture))};
-
+        auto args = Vector<core::ir::Value*, 1>{b.Constant(u32(type))};
         auto* call = b.Call<msl::ir::BuiltinCall>(
             builtin->Result(0)->Type(), msl::BuiltinFn::kThreadgroupBarrier, std::move(args));
         call->InsertBefore(builtin);
diff --git a/src/tint/lang/spirv/reader/ast_lower/atomics.cc b/src/tint/lang/spirv/reader/ast_lower/atomics.cc
index 181ab19..8b74df7 100644
--- a/src/tint/lang/spirv/reader/ast_lower/atomics.cc
+++ b/src/tint/lang/spirv/reader/ast_lower/atomics.cc
@@ -224,7 +224,7 @@
                 }
                 auto count = arr->ConstantCount();
                 if (!count) {
-                    ctx.dst->Diagnostics().AddError(diag::System::Transform, Source{})
+                    ctx.dst->Diagnostics().AddError(Source{})
                         << "the Atomics transform does not currently support array counts that use "
                            "override values";
                     count = 1;
diff --git a/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc b/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
index e4cf3ef..efb14a2 100644
--- a/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
@@ -29,7 +29,7 @@
 
 #include <algorithm>
 #include <limits>
-#include <locale>
+#include <string_view>
 #include <utility>
 
 #include "source/opt/build_module.h"
@@ -775,11 +775,10 @@
     return true;
 }
 
-bool ASTParser::IsValidIdentifier(const std::string& str) {
+bool ASTParser::IsValidIdentifier(std::string_view 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
@@ -787,14 +786,28 @@
             // 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)) {
+
+    // Must begin with an XID_Source unicode character, or underscore
+    {
+        auto* utf8 = reinterpret_cast<const uint8_t*>(str.data());
+        auto [code_point, n] = tint::utf8::Decode(utf8, str.size());
+        if (code_point != tint::CodePoint('_') && !code_point.IsXIDStart()) {
             return false;
         }
+        str = str.substr(n);
     }
+
+    // Must continue with an XID_Continue unicode character
+    while (!str.empty()) {
+        auto* utf8 = reinterpret_cast<const uint8_t*>(str.data());
+        auto [code_point, n] = tint::utf8::Decode(utf8, str.size());
+        if (!code_point.IsXIDContinue()) {
+            return false;
+        }
+        str = str.substr(n);
+    }
+
     return true;
 }
 
diff --git a/src/tint/lang/spirv/reader/ast_parser/ast_parser.h b/src/tint/lang/spirv/reader/ast_parser/ast_parser.h
index 86f60e7..8f755f7 100644
--- a/src/tint/lang/spirv/reader/ast_parser/ast_parser.h
+++ b/src/tint/lang/spirv/reader/ast_parser/ast_parser.h
@@ -628,7 +628,7 @@
 
     /// @param str a candidate identifier
     /// @returns true if the given string is a valid WGSL identifier.
-    static bool IsValidIdentifier(const std::string& str);
+    static bool IsValidIdentifier(std::string_view str);
 
     /// Returns true if the given SPIR-V ID is a declared specialization constant,
     /// generated by one of OpConstantTrue, OpConstantFalse, or OpSpecConstant
diff --git a/src/tint/lang/spirv/reader/ast_parser/barrier_test.cc b/src/tint/lang/spirv/reader/ast_parser/barrier_test.cc
index 7fa6c89..aef7e09 100644
--- a/src/tint/lang/spirv/reader/ast_parser/barrier_test.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/barrier_test.cc
@@ -51,7 +51,7 @@
     auto p = std::make_unique<ASTParser>(test::Assemble(preamble + spirv));
     if (!p->BuildAndParseInternalModule()) {
         ProgramBuilder builder;
-        builder.Diagnostics().AddError(diag::System::Reader, Source{}) << p->error();
+        builder.Diagnostics().AddError(Source{}) << p->error();
         return Program(std::move(builder));
     }
     return p->Program();
diff --git a/src/tint/lang/spirv/reader/ast_parser/parse.cc b/src/tint/lang/spirv/reader/ast_parser/parse.cc
index ac50d1e..48f9443 100644
--- a/src/tint/lang/spirv/reader/ast_parser/parse.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/parse.cc
@@ -82,7 +82,7 @@
 
     ProgramBuilder& builder = parser.builder();
     if (!parsed) {
-        builder.Diagnostics().AddError(diag::System::Reader, Source{}) << parser.error();
+        builder.Diagnostics().AddError(Source{}) << parser.error();
         return Program(std::move(builder));
     }
 
diff --git a/src/tint/lang/spirv/reader/lower/vector_element_pointer_test.cc b/src/tint/lang/spirv/reader/lower/vector_element_pointer_test.cc
index ade391c..bd30d05 100644
--- a/src/tint/lang/spirv/reader/lower/vector_element_pointer_test.cc
+++ b/src/tint/lang/spirv/reader/lower/vector_element_pointer_test.cc
@@ -42,16 +42,17 @@
 TEST_F(SpirvReader_VectorElementPointerTest, NonPointerAccess) {
     auto* vec = b.FunctionParam("vec", ty.vec4<u32>());
     auto* foo = b.Function("foo", ty.u32());
+    foo->SetParams({vec});
     b.Append(foo->Block(), [&] {
         auto* access = b.Access<u32>(vec, 2_u);
         b.Return(foo, access);
     });
 
     auto* src = R"(
-%foo = func():u32 {
+%foo = func(%vec:vec4<u32>):u32 {
   $B1: {
-    %2:u32 = access %vec, 2u
-    ret %2
+    %3:u32 = access %vec, 2u
+    ret %3
   }
 }
 )";
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.bazel b/src/tint/lang/spirv/reader/parser/BUILD.bazel
index 22ae352..556a767 100644
--- a/src/tint/lang/spirv/reader/parser/BUILD.bazel
+++ b/src/tint/lang/spirv/reader/parser/BUILD.bazel
@@ -79,6 +79,7 @@
   name = "test",
   alwayslink = True,
   srcs = [
+    "binary_test.cc",
     "composite_test.cc",
     "constant_test.cc",
     "function_test.cc",
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.cmake b/src/tint/lang/spirv/reader/parser/BUILD.cmake
index 52c7c0c..20ea525 100644
--- a/src/tint/lang/spirv/reader/parser/BUILD.cmake
+++ b/src/tint/lang/spirv/reader/parser/BUILD.cmake
@@ -85,6 +85,7 @@
 # Condition: TINT_BUILD_SPV_READER
 ################################################################################
 tint_add_target(tint_lang_spirv_reader_parser_test test
+  lang/spirv/reader/parser/binary_test.cc
   lang/spirv/reader/parser/composite_test.cc
   lang/spirv/reader/parser/constant_test.cc
   lang/spirv/reader/parser/function_test.cc
diff --git a/src/tint/lang/spirv/reader/parser/BUILD.gn b/src/tint/lang/spirv/reader/parser/BUILD.gn
index f6c3fba..618d5d5 100644
--- a/src/tint/lang/spirv/reader/parser/BUILD.gn
+++ b/src/tint/lang/spirv/reader/parser/BUILD.gn
@@ -86,6 +86,7 @@
   if (tint_build_spv_reader) {
     tint_unittests_source_set("unittests") {
       sources = [
+        "binary_test.cc",
         "composite_test.cc",
         "constant_test.cc",
         "function_test.cc",
diff --git a/src/tint/lang/spirv/reader/parser/binary_test.cc b/src/tint/lang/spirv/reader/parser/binary_test.cc
new file mode 100644
index 0000000..bf5d71a
--- /dev/null
+++ b/src/tint/lang/spirv/reader/parser/binary_test.cc
@@ -0,0 +1,161 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "src/tint/lang/spirv/reader/parser/helper_test.h"
+
+namespace tint::spirv::reader {
+namespace {
+
+struct BinaryCase {
+    std::string spirv_type;
+    std::string spirv_opcode;
+    std::string ir;
+};
+std::string PrintBuiltinCase(testing::TestParamInfo<BinaryCase> bc) {
+    return bc.param.spirv_opcode + "_" + bc.param.spirv_type;
+}
+
+using BinaryTest = SpirvParserTestWithParam<BinaryCase>;
+
+TEST_P(BinaryTest, All) {
+    auto params = GetParam();
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpCapability Float16
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint GLCompute %main "main"
+               OpExecutionMode %main LocalSize 1 1 1
+       %void = OpTypeVoid
+        %i32 = OpTypeInt 32 1
+        %u32 = OpTypeInt 32 0
+        %f16 = OpTypeFloat 16
+        %f32 = OpTypeFloat 32
+      %vec3i = OpTypeVector %i32 3
+      %vec4u = OpTypeVector %u32 4
+      %vec3h = OpTypeVector %f16 3
+      %vec4f = OpTypeVector %f32 4
+    %ep_type = OpTypeFunction %void
+    %fn_type = OpTypeFunction %)" +
+                  params.spirv_type + " %" + params.spirv_type + " %" + params.spirv_type + R"(
+       %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+               OpReturn
+               OpFunctionEnd
+
+        %foo = OpFunction %)" +
+                  params.spirv_type + " " + R"( None %fn_type
+        %lhs = OpFunctionParameter %)" +
+                  params.spirv_type + " " + R"(
+        %rhs = OpFunctionParameter %)" +
+                  params.spirv_type + " " + R"(
+  %foo_start = OpLabel
+     %result = )" +
+                  params.spirv_opcode + R"( %)" + params.spirv_type + " " + R"( %lhs %rhs
+               OpReturnValue %result
+               OpFunctionEnd
+)",
+              R"(
+  $B2: {
+    )" + params.ir +
+                  R"(
+    ret %5
+  }
+)");
+}
+
+INSTANTIATE_TEST_SUITE_P(SpirvParser,
+                         BinaryTest,
+                         testing::Values(
+                             // OpFAdd
+                             BinaryCase{
+                                 "f16",
+                                 "OpFAdd",
+                                 "%5:f16 = add %3, %4",
+                             },
+                             BinaryCase{
+                                 "f32",
+                                 "OpFAdd",
+                                 "%5:f32 = add %3, %4",
+                             },
+                             BinaryCase{
+                                 "vec3h",
+                                 "OpFAdd",
+                                 "%5:vec3<f16> = add %3, %4",
+                             },
+                             BinaryCase{
+                                 "vec4f",
+                                 "OpFAdd",
+                                 "%5:vec4<f32> = add %3, %4",
+                             },
+
+                             // OpFMul
+                             BinaryCase{
+                                 "f16",
+                                 "OpFMul",
+                                 "%5:f16 = mul %3, %4",
+                             },
+                             BinaryCase{
+                                 "f32",
+                                 "OpFMul",
+                                 "%5:f32 = mul %3, %4",
+                             },
+                             BinaryCase{
+                                 "vec3h",
+                                 "OpFMul",
+                                 "%5:vec3<f16> = mul %3, %4",
+                             },
+                             BinaryCase{
+                                 "vec4f",
+                                 "OpFMul",
+                                 "%5:vec4<f32> = mul %3, %4",
+                             },
+
+                             // OpIAdd
+                             BinaryCase{
+                                 "i32",
+                                 "OpIAdd",
+                                 "%5:i32 = add %3, %4",
+                             },
+                             BinaryCase{
+                                 "u32",
+                                 "OpIAdd",
+                                 "%5:u32 = add %3, %4",
+                             },
+                             BinaryCase{
+                                 "vec3i",
+                                 "OpIAdd",
+                                 "%5:vec3<i32> = add %3, %4",
+                             },
+                             BinaryCase{
+                                 "vec4u",
+                                 "OpIAdd",
+                                 "%5:vec4<u32> = add %3, %4",
+                             }),
+                         PrintBuiltinCase);
+
+}  // namespace
+}  // namespace tint::spirv::reader
diff --git a/src/tint/lang/spirv/reader/parser/function_test.cc b/src/tint/lang/spirv/reader/parser/function_test.cc
index d268ebb..d344063 100644
--- a/src/tint/lang/spirv/reader/parser/function_test.cc
+++ b/src/tint/lang/spirv/reader/parser/function_test.cc
@@ -96,6 +96,40 @@
 )");
 }
 
+TEST_F(SpirvParserTest, FragmentShader_DepthReplacing) {
+    EXPECT_IR(R"(
+               OpCapability Shader
+               OpMemoryModel Logical GLSL450
+               OpEntryPoint Fragment %main "main" %depth
+               OpExecutionMode %main OriginUpperLeft
+               OpExecutionMode %main DepthReplacing
+               OpDecorate %depth BuiltIn FragDepth
+       %void = OpTypeVoid
+        %f32 = OpTypeFloat 32
+     %f32_42 = OpConstant %f32 42.0
+%_ptr_Output_f32 = OpTypePointer Output %f32
+      %depth = OpVariable %_ptr_Output_f32 Output
+    %ep_type = OpTypeFunction %void
+       %main = OpFunction %void None %ep_type
+ %main_start = OpLabel
+               OpStore %depth %f32_42
+               OpReturn
+               OpFunctionEnd
+)",
+              R"(
+$B1: {  # root
+  %1:ptr<__out, f32, read_write> = var @builtin(frag_depth)
+}
+
+%main = @fragment func():void {
+  $B2: {
+    store %1, 42.0f
+    ret
+  }
+}
+)");
+}
+
 TEST_F(SpirvParserTest, VertexShader) {
     EXPECT_IR(R"(
                OpCapability Shader
diff --git a/src/tint/lang/spirv/reader/parser/helper_test.h b/src/tint/lang/spirv/reader/parser/helper_test.h
index cf510d3..865aa4c 100644
--- a/src/tint/lang/spirv/reader/parser/helper_test.h
+++ b/src/tint/lang/spirv/reader/parser/helper_test.h
@@ -34,7 +34,7 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/spirv/reader/common/helper_test.h"
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index 6023194..8fadddf 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -487,7 +487,9 @@
                                            execution_mode.GetSingleWordInOperand(3),
                                            execution_mode.GetSingleWordInOperand(4));
                     break;
+                case spv::ExecutionMode::DepthReplacing:
                 case spv::ExecutionMode::OriginUpperLeft:
+                    // These are ignored as they are implicitly supported by Tint IR.
                     break;
                 default:
                     TINT_UNIMPLEMENTED() << "unhandled execution mode: " << mode;
@@ -512,9 +514,18 @@
                 case spv::Op::OpCompositeExtract:
                     EmitCompositeExtract(inst);
                     break;
+                case spv::Op::OpFAdd:
+                    EmitBinary(inst, core::BinaryOp::kAdd);
+                    break;
+                case spv::Op::OpFMul:
+                    EmitBinary(inst, core::BinaryOp::kMultiply);
+                    break;
                 case spv::Op::OpFunctionCall:
                     EmitFunctionCall(inst);
                     break;
+                case spv::Op::OpIAdd:
+                    EmitBinary(inst, core::BinaryOp::kAdd);
+                    break;
                 case spv::Op::OpLoad:
                     Emit(b_.Load(Value(inst.GetSingleWordOperand(2))), inst.result_id());
                     break;
@@ -556,6 +567,15 @@
         Emit(access, inst.result_id());
     }
 
+    /// @param inst the SPIR-V instruction
+    /// @param op the binary operator to use
+    void EmitBinary(const spvtools::opt::Instruction& inst, core::BinaryOp op) {
+        auto* lhs = Value(inst.GetSingleWordOperand(2));
+        auto* rhs = Value(inst.GetSingleWordOperand(3));
+        auto* binary = b_.Binary(op, Type(inst.type_id()), lhs, rhs);
+        Emit(binary, inst.result_id());
+    }
+
     /// @param inst the SPIR-V instruction for OpCompositeExtract
     void EmitCompositeExtract(const spvtools::opt::Instruction& inst) {
         Vector<core::ir::Value*, 4> indices;
diff --git a/src/tint/lang/spirv/reader/reader_test.cc b/src/tint/lang/spirv/reader/reader_test.cc
index cd0626b..d8ab351 100644
--- a/src/tint/lang/spirv/reader/reader_test.cc
+++ b/src/tint/lang/spirv/reader/reader_test.cc
@@ -31,7 +31,7 @@
 
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/core/ir/module.h"
 #include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/spirv/reader/common/helper_test.h"
diff --git a/src/tint/lang/spirv/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/spirv/writer/ast_printer/ast_printer_test.cc
index e76c3c2..48c2a3d 100644
--- a/src/tint/lang/spirv/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/ast_printer_test.cc
@@ -34,7 +34,7 @@
 using SpirvASTPrinterTest = TestHelper;
 
 TEST_F(SpirvASTPrinterTest, InvalidProgram) {
-    Diagnostics().AddError(diag::System::Writer, Source{}) << "make the program invalid";
+    Diagnostics().AddError(Source{}) << "make the program invalid";
     ASSERT_FALSE(IsValid());
     auto program = resolver::Resolve(*this);
     ASSERT_FALSE(program.IsValid());
diff --git a/src/tint/lang/spirv/writer/common/option_helper.cc b/src/tint/lang/spirv/writer/common/option_helper.cc
index 76a21ed..f209b63 100644
--- a/src/tint/lang/spirv/writer/common/option_helper.cc
+++ b/src/tint/lang/spirv/writer/common/option_helper.cc
@@ -48,8 +48,7 @@
                                                          const binding::BindingInfo& dst) -> bool {
         if (auto binding = seen_wgsl_bindings.Get(src)) {
             if (*binding != dst) {
-                diagnostics.AddError(diag::System::Writer, Source{})
-                    << "found duplicate WGSL binding point: " << src;
+                diagnostics.AddError(Source{}) << "found duplicate WGSL binding point: " << src;
                 return true;
             }
         }
@@ -61,7 +60,7 @@
                                                            const tint::BindingPoint& dst) -> bool {
         if (auto binding = seen_spirv_bindings.Get(src)) {
             if (*binding != dst) {
-                diagnostics.AddError(diag::System::Writer, Source{})
+                diagnostics.AddError(Source{})
                     << "found duplicate SPIR-V binding point: [group: " << src.group
                     << ", binding: " << src.binding << "]";
                 return true;
@@ -88,23 +87,23 @@
     };
 
     if (!valid(options.bindings.uniform)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing uniform";
+        diagnostics.AddNote(Source{}) << "when processing uniform";
         return Failure{std::move(diagnostics)};
     }
     if (!valid(options.bindings.storage)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing storage";
+        diagnostics.AddNote(Source{}) << "when processing storage";
         return Failure{std::move(diagnostics)};
     }
     if (!valid(options.bindings.texture)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing texture";
+        diagnostics.AddNote(Source{}) << "when processing texture";
         return Failure{std::move(diagnostics)};
     }
     if (!valid(options.bindings.storage_texture)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing storage_texture";
+        diagnostics.AddNote(Source{}) << "when processing storage_texture";
         return Failure{std::move(diagnostics)};
     }
     if (!valid(options.bindings.sampler)) {
-        diagnostics.AddNote(diag::System::Writer, Source{}) << "when processing sampler";
+        diagnostics.AddNote(Source{}) << "when processing sampler";
         return Failure{std::move(diagnostics)};
     }
 
@@ -116,24 +115,20 @@
 
         // Validate with the actual source regardless of what the remapper will do
         if (wgsl_seen(src_binding, plane0)) {
-            diagnostics.AddNote(diag::System::Writer, Source{})
-                << "when processing external_texture";
+            diagnostics.AddNote(Source{}) << "when processing external_texture";
             return Failure{std::move(diagnostics)};
         }
 
         if (spirv_seen(plane0, src_binding)) {
-            diagnostics.AddNote(diag::System::Writer, Source{})
-                << "when processing external_texture";
+            diagnostics.AddNote(Source{}) << "when processing external_texture";
             return Failure{std::move(diagnostics)};
         }
         if (spirv_seen(plane1, src_binding)) {
-            diagnostics.AddNote(diag::System::Writer, Source{})
-                << "when processing external_texture";
+            diagnostics.AddNote(Source{}) << "when processing external_texture";
             return Failure{std::move(diagnostics)};
         }
         if (spirv_seen(metadata, src_binding)) {
-            diagnostics.AddNote(diag::System::Writer, Source{})
-                << "when processing external_texture";
+            diagnostics.AddNote(Source{}) << "when processing external_texture";
             return Failure{std::move(diagnostics)};
         }
     }
diff --git a/src/tint/lang/spirv/writer/raise/merge_return_test.cc b/src/tint/lang/spirv/writer/raise/merge_return_test.cc
index 75998f5..40d44b0 100644
--- a/src/tint/lang/spirv/writer/raise/merge_return_test.cc
+++ b/src/tint/lang/spirv/writer/raise/merge_return_test.cc
@@ -67,7 +67,7 @@
     auto* in = b.FunctionParam(ty.i32());
     auto* cond = b.FunctionParam(ty.bool_());
     auto* func = b.Function("foo", ty.i32());
-    func->SetParams({in});
+    func->SetParams({in, cond});
 
     b.Append(func->Block(), [&] {
         auto* ifelse = b.If(cond);
@@ -78,9 +78,9 @@
         b.Return(func, ifelse->Result(0));
     });
     auto* src = R"(
-%foo = func(%2:i32):i32 {
+%foo = func(%2:i32, %3:bool):i32 {
   $B1: {
-    %3:i32 = if %4 [t: $B2, f: $B3] {  # if_1
+    %4:i32 = if %3 [t: $B2, f: $B3] {  # if_1
       $B2: {  # true
         %5:i32 = add %2, 1i
         exit_if %5  # if_1
@@ -90,7 +90,7 @@
         exit_if %6  # if_1
       }
     }
-    ret %3
+    ret %4
   }
 }
 )";
@@ -107,7 +107,7 @@
     auto* in = b.FunctionParam(ty.i32());
     auto* cond = b.FunctionParam(ty.bool_());
     auto* func = b.Function("foo", ty.i32());
-    func->SetParams({in});
+    func->SetParams({in, cond});
 
     b.Append(func->Block(), [&] {
         auto* swtch = b.Switch(in);
@@ -125,7 +125,7 @@
     });
 
     auto* src = R"(
-%foo = func(%2:i32):i32 {
+%foo = func(%2:i32, %3:bool):i32 {
   $B1: {
     switch %2 [c: (default, $B2)] {  # switch_1
       $B2: {  # case
@@ -137,7 +137,7 @@
         exit_loop  # loop_1
       }
     }
-    %3:i32 = if %4 [t: $B4, f: $B5] {  # if_1
+    %4:i32 = if %3 [t: $B4, f: $B5] {  # if_1
       $B4: {  # true
         %5:i32 = add %2, 1i
         exit_if %5  # if_1
@@ -147,7 +147,7 @@
         exit_if %6  # if_1
       }
     }
-    ret %3
+    ret %4
   }
 }
 )";
diff --git a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index_test.cc b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index_test.cc
index 59b0f27..e1a9da3 100644
--- a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index_test.cc
+++ b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index_test.cc
@@ -587,11 +587,11 @@
       $B2 (%5:array<i32, 4>): {  # body
         if %2 [t: $B3, f: $B4] {  # if_1
           $B3: {  # true
-            %6:i32 = access %5:array<i32, 4>, %3
+            %6:i32 = access %5, %3
             ret %6
           }
           $B4: {  # false
-            %7:i32 = access %5:array<i32, 4>, %4
+            %7:i32 = access %5, %4
             ret %7
           }
         }
@@ -609,7 +609,7 @@
   $B1: {
     loop [b: $B2] {  # loop_1
       $B2 (%5:array<i32, 4>): {  # body
-        %6:ptr<function, array<i32, 4>, read_write> = var, %5:array<i32, 4>
+        %6:ptr<function, array<i32, 4>, read_write> = var, %5
         if %2 [t: $B3, f: $B4] {  # if_1
           $B3: {  # true
             %7:ptr<function, i32, read_write> = access %6, %3
@@ -666,11 +666,11 @@
       $B2 (%5:array<array<i32, 4>, 4>): {  # body
         if %2 [t: $B3, f: $B4] {  # if_1
           $B3: {  # true
-            %6:i32 = access %5:array<array<i32, 4>, 4>, 0u, %3
+            %6:i32 = access %5, 0u, %3
             ret %6
           }
           $B4: {  # false
-            %7:i32 = access %5:array<array<i32, 4>, 4>, 0u, %4
+            %7:i32 = access %5, 0u, %4
             ret %7
           }
         }
@@ -688,7 +688,7 @@
   $B1: {
     loop [b: $B2] {  # loop_1
       $B2 (%5:array<array<i32, 4>, 4>): {  # body
-        %6:array<i32, 4> = access %5:array<array<i32, 4>, 4>, 0u
+        %6:array<i32, 4> = access %5, 0u
         %7:ptr<function, array<i32, 4>, read_write> = var, %6
         if %2 [t: $B3, f: $B4] {  # if_1
           $B3: {  # true
diff --git a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc
index 636f8f3..40c7fc2 100644
--- a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc
+++ b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.cc
@@ -97,9 +97,8 @@
     /// @returns the new program or SkipTransform if the transform is not required
     ApplyResult Run() {
         if (cfg == nullptr) {
-            b.Diagnostics().AddError(diag::System::Transform, Source{})
-                << "missing transform data for "
-                << tint::TypeInfo::Of<ArrayLengthFromUniform>().name;
+            b.Diagnostics().AddError(Source{}) << "missing transform data for "
+                                               << tint::TypeInfo::Of<ArrayLengthFromUniform>().name;
             return resolver::Resolve(b);
         }
 
diff --git a/src/tint/lang/wgsl/ast/transform/binding_remapper.cc b/src/tint/lang/wgsl/ast/transform/binding_remapper.cc
index 5501dd5..d89d8d8 100644
--- a/src/tint/lang/wgsl/ast/transform/binding_remapper.cc
+++ b/src/tint/lang/wgsl/ast/transform/binding_remapper.cc
@@ -67,8 +67,7 @@
 
     auto* remappings = inputs.Get<Remappings>();
     if (!remappings) {
-        b.Diagnostics().AddError(diag::System::Transform, Source{})
-            << "missing transform data for " << TypeInfo().name;
+        b.Diagnostics().AddError(Source{}) << "missing transform data for " << TypeInfo().name;
         return resolver::Resolve(b);
     }
 
@@ -113,13 +112,13 @@
             if (ac_it != remappings->access_controls.end()) {
                 core::Access access = ac_it->second;
                 if (access == core::Access::kUndefined) {
-                    b.Diagnostics().AddError(diag::System::Transform, Source{})
+                    b.Diagnostics().AddError(Source{})
                         << "invalid access mode (" << static_cast<uint32_t>(access) << ")";
                     return resolver::Resolve(b);
                 }
                 auto* sem = src.Sem().Get(var);
                 if (sem->AddressSpace() != core::AddressSpace::kStorage) {
-                    b.Diagnostics().AddError(diag::System::Transform, Source{})
+                    b.Diagnostics().AddError(Source{})
                         << "cannot apply access control to variable with address space "
                         << sem->AddressSpace();
                     return resolver::Resolve(b);
diff --git a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
index e61f615..206a8d3 100644
--- a/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
+++ b/src/tint/lang/wgsl/ast/transform/canonicalize_entry_point_io.cc
@@ -979,8 +979,7 @@
 
     auto* cfg = inputs.Get<Config>();
     if (cfg == nullptr) {
-        b.Diagnostics().AddError(diag::System::Transform, Source{})
-            << "missing transform data for " << TypeInfo().name;
+        b.Diagnostics().AddError(Source{}) << "missing transform data for " << TypeInfo().name;
         return resolver::Resolve(b);
     }
 
diff --git a/src/tint/lang/wgsl/ast/transform/clamp_frag_depth_fuzz.cc b/src/tint/lang/wgsl/ast/transform/clamp_frag_depth_fuzz.cc
index d2ef4c8..2854240 100644
--- a/src/tint/lang/wgsl/ast/transform/clamp_frag_depth_fuzz.cc
+++ b/src/tint/lang/wgsl/ast/transform/clamp_frag_depth_fuzz.cc
@@ -31,7 +31,18 @@
 namespace tint::ast::transform {
 namespace {
 
+bool CanRun(const ClampFragDepth::Config& config) {
+    if (config.offsets && config.offsets->min >= config.offsets->max) {
+        return false;  // member offset collision / non-ascending
+    }
+    return true;
+}
+
 void ClampFragDepthFuzzer(const Program& program, const ClampFragDepth::Config& config) {
+    if (!CanRun(config)) {
+        return;
+    }
+
     DataMap inputs;
     inputs.Add<ClampFragDepth::Config>(std::move(config));
 
diff --git a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.cc b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.cc
index 3d7450b..51b5f1e 100644
--- a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.cc
+++ b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture.cc
@@ -135,7 +135,7 @@
 
             BindingsMap::const_iterator it = new_binding_points->bindings_map.find(bp);
             if (it == new_binding_points->bindings_map.end()) {
-                b.Diagnostics().AddError(diag::System::Transform, Source{})
+                b.Diagnostics().AddError(Source{})
                     << "missing new binding points for texture_external at binding {" << bp.group
                     << "," << bp.binding << "}";
                 continue;
@@ -295,7 +295,13 @@
             b.Member("gammaEncodeParams", b.ty("GammaTransferParams")),
             b.Member("gamutConversionMatrix", b.ty.mat3x3<f32>()),
             b.Member("coordTransformationMatrix", b.ty.mat3x2<f32>()),
-        };
+            b.Member("loadTransformationMatrix", b.ty.mat3x2<f32>()),
+            b.Member("samplePlane0RectMin", b.ty.vec2<f32>()),
+            b.Member("samplePlane0RectMax", b.ty.vec2<f32>()),
+            b.Member("samplePlane1RectMin", b.ty.vec2<f32>()),
+            b.Member("samplePlane1RectMax", b.ty.vec2<f32>()),
+            b.Member("displayVisibleRectMax", b.ty.vec2<u32>()),
+            b.Member("plane1CoordFactor", b.ty.vec2<f32>())};
 
         params_struct_sym = b.Symbols().New("ExternalTextureParams");
 
@@ -344,72 +350,90 @@
     /// @returns a statement list that makes of the body of the chosen function
     auto buildTextureBuiltinBody(wgsl::BuiltinFn call_type) {
         tint::Vector<const Statement*, 16> stmts;
-        const CallExpression* single_plane_call = nullptr;
-        const CallExpression* plane_0_call = nullptr;
-        const CallExpression* plane_1_call = nullptr;
+        const BlockStatement* single_plane_block = nullptr;
+        const BlockStatement* multi_plane_block = nullptr;
         switch (call_type) {
             case wgsl::BuiltinFn::kTextureSampleBaseClampToEdge:
                 stmts.Push(b.Decl(b.Let(
                     "modifiedCoords", b.Mul(b.MemberAccessor("params", "coordTransformationMatrix"),
                                             b.Call<vec3<f32>>("coord", 1_a)))));
 
-                stmts.Push(b.Decl(
-                    b.Let("plane0_dims",
-                          b.Call(b.ty.vec2<f32>(), b.Call("textureDimensions", "plane0", 0_a)))));
-                stmts.Push(b.Decl(
-                    b.Let("plane0_half_texel", b.Div(b.Call<vec2<f32>>(0.5_a), "plane0_dims"))));
-                stmts.Push(b.Decl(
-                    b.Let("plane0_clamped", b.Call("clamp", "modifiedCoords", "plane0_half_texel",
-                                                   b.Sub(1_a, "plane0_half_texel")))));
-                stmts.Push(b.Decl(
-                    b.Let("plane1_dims",
-                          b.Call(b.ty.vec2<f32>(), b.Call("textureDimensions", "plane1", 0_a)))));
-                stmts.Push(b.Decl(
-                    b.Let("plane1_half_texel", b.Div(b.Call<vec2<f32>>(0.5_a), "plane1_dims"))));
-                stmts.Push(b.Decl(
-                    b.Let("plane1_clamped", b.Call("clamp", "modifiedCoords", "plane1_half_texel",
-                                                   b.Sub(1_a, "plane1_half_texel")))));
+                stmts.Push(b.Decl(b.Let(
+                    "plane0_clamped", b.Call("clamp", "modifiedCoords",
+                                             b.MemberAccessor("params", "samplePlane0RectMin"),
+                                             b.MemberAccessor("params", "samplePlane0RectMax")))));
 
-                // textureSampleLevel(plane0, smp, plane0_clamped, 0.0);
-                single_plane_call =
-                    b.Call("textureSampleLevel", "plane0", "smp", "plane0_clamped", 0_a);
-                // textureSampleLevel(plane0, smp, plane0_clamped, 0.0);
-                plane_0_call = b.Call("textureSampleLevel", "plane0", "smp", "plane0_clamped", 0_a);
-                // textureSampleLevel(plane1, smp, plane1_clamped, 0.0);
-                plane_1_call = b.Call("textureSampleLevel", "plane1", "smp", "plane1_clamped", 0_a);
+                // var color: vec4<f32>;
+                stmts.Push(b.Decl(b.Var("color", b.ty.vec4(b.ty.f32()))));
+
+                single_plane_block = b.Block(
+                    b.Assign("color", b.MemberAccessor(b.Call("textureSampleLevel", "plane0", "smp",
+                                                              "plane0_clamped", 0_a),
+                                                       "rgba")));
+
+                multi_plane_block = b.Block(
+                    b.Decl(b.Let("plane1_clamped",
+                                 b.Call("clamp", "modifiedCoords",
+                                        b.MemberAccessor("params", "samplePlane1RectMin"),
+                                        b.MemberAccessor("params", "samplePlane1RectMax")))),
+
+                    b.Assign("color",
+                             b.Call<vec4<f32>>(
+                                 b.Mul(b.Call<vec4<f32>>(
+                                           b.MemberAccessor(b.Call("textureSampleLevel", "plane0",
+                                                                   "smp", "plane0_clamped", 0_a),
+                                                            "r"),
+                                           b.MemberAccessor(b.Call("textureSampleLevel", "plane1",
+                                                                   "smp", "plane1_clamped", 0_a),
+                                                            "rg"),
+                                           1_a),
+                                       b.MemberAccessor("params", "yuvToRgbConversionMatrix")),
+                                 1_a)));
                 break;
             case wgsl::BuiltinFn::kTextureLoad:
-                // textureLoad(plane0, coord, 0);
-                single_plane_call = b.Call("textureLoad", "plane0", "coord", 0_a);
-                // textureLoad(plane0, coord, 0);
-                plane_0_call = b.Call("textureLoad", "plane0", "coord", 0_a);
-                // let coord1 = coord >> 1;
-                stmts.Push(b.Decl(b.Let("coord1", b.Shr("coord", b.Call<vec2<u32>>(1_a)))));
-                // textureLoad(plane1, coord1, 0);
-                plane_1_call = b.Call("textureLoad", "plane1", "coord1", 0_a);
+                stmts.Push(b.Decl(b.Let(
+                    "clampedCoords", b.Call("min", b.Call<vec2<u32>>("coord"),
+                                            b.MemberAccessor("params", "displayVisibleRectMax")))));
+                stmts.Push(b.Decl(b.Let(
+                    "plane0_clamped",
+                    b.Call<vec2<u32>>(b.Call(
+                        "round",
+                        b.Mul(b.MemberAccessor("params", "loadTransformationMatrix"),
+                              b.Call<vec3<f32>>(b.Call<vec2<f32>>("clampedCoords"), 1_a)))))));
+
+                // var color: vec4<f32>;
+                stmts.Push(b.Decl(b.Var("color", b.ty.vec4(b.ty.f32()))));
+
+                single_plane_block = b.Block(b.Assign(
+                    "color", b.MemberAccessor(
+                                 b.Call("textureLoad", "plane0", "plane0_clamped", 0_a), "rgba")));
+
+                multi_plane_block = b.Block(
+                    b.Decl(b.Let(
+                        "plane1_clamped",
+                        b.Call<vec2<u32>>(b.Mul(b.Call<vec2<f32>>("plane0_clamped"),
+                                                b.MemberAccessor("params", "plane1CoordFactor"))))),
+
+                    b.Assign("color",
+                             b.Call<vec4<f32>>(
+                                 b.Mul(b.Call<vec4<f32>>(
+                                           b.MemberAccessor(b.Call("textureLoad", "plane0",
+                                                                   "plane0_clamped", 0_a),
+                                                            "r"),
+                                           b.MemberAccessor(b.Call("textureLoad", "plane1",
+                                                                   "plane1_clamped", 0_a),
+                                                            "rg"),
+                                           1_a),
+                                       b.MemberAccessor("params", "yuvToRgbConversionMatrix")),
+                                 1_a)));
                 break;
             default:
                 TINT_ICE() << "unhandled builtin: " << call_type;
         }
 
-        // var color: vec4<f32>;
-        stmts.Push(b.Decl(b.Var("color", b.ty.vec4(b.ty.f32()))));
-
         // if ((params.numPlanes == 1u))
-        stmts.Push(b.If(
-            b.Equal(b.MemberAccessor("params", "numPlanes"), b.Expr(1_a)),
-            b.Block(
-                // color = textureLoad(plane0, coord, 0).rgba;
-                b.Assign("color", b.MemberAccessor(single_plane_call, "rgba"))),
-            b.Else(b.Block(
-                // color = vec4<f32>(vec4<f32>(plane_0_call.r, plane_1_call.rg, 1.0) *
-                //         params.yuvToRgbConversionMatrix));
-                b.Assign("color",
-                         b.Call<vec4<f32>>(
-                             b.Mul(b.Call<vec4<f32>>(b.MemberAccessor(plane_0_call, "r"),
-                                                     b.MemberAccessor(plane_1_call, "rg"), 1_a),
-                                   b.MemberAccessor("params", "yuvToRgbConversionMatrix")),
-                             1_a))))));
+        stmts.Push(b.If(b.Equal(b.MemberAccessor("params", "numPlanes"), b.Expr(1_a)),
+                        single_plane_block, b.Else(multi_plane_block)));
 
         // if (params.doYuvToRgbConversionOnly == 0u)
         stmts.Push(b.If(
@@ -558,7 +582,7 @@
     ProgramBuilder b;
     program::CloneContext ctx{&b, &src, /* auto_clone_symbols */ true};
     if (!new_binding_points) {
-        b.Diagnostics().AddError(diag::System::Transform, Source{})
+        b.Diagnostics().AddError(Source{})
             << "missing new binding point data for " << TypeInfo().name;
         return resolver::Resolve(b);
     }
diff --git a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture_test.cc b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture_test.cc
index f89523c..8f78af9 100644
--- a/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture_test.cc
+++ b/src/tint/lang/wgsl/ast/transform/multiplanar_external_texture_test.cc
@@ -153,6 +153,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
@@ -206,6 +213,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @internal(disable_validation__binding_point_collision) @group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
@@ -222,12 +236,14 @@
 }
 
 fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<u32>, params : ExternalTextureParams) -> vec4<f32> {
-  let coord1 = (coord >> vec2<u32>(1));
+  let clampedCoords = min(vec2<u32>(coord), params.displayVisibleRectMax);
+  let plane0_clamped = vec2<u32>(round((params.loadTransformationMatrix * vec3<f32>(vec2<f32>(clampedCoords), 1))));
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
-    color = textureLoad(plane0, coord, 0).rgba;
+    color = textureLoad(plane0, plane0_clamped, 0).rgba;
   } else {
-    color = vec4<f32>((vec4<f32>(textureLoad(plane0, coord, 0).r, textureLoad(plane1, coord1, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
+    let plane1_clamped = vec2<u32>((vec2<f32>(plane0_clamped) * params.plane1CoordFactor));
+    color = vec4<f32>((vec4<f32>(textureLoad(plane0, plane0_clamped, 0).r, textureLoad(plane1, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
     color = vec4<f32>(gammaCorrection(color.rgb, params.gammaDecodeParams), color.a);
@@ -286,6 +302,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
@@ -341,6 +364,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -360,16 +390,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -425,6 +451,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -440,16 +473,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -510,6 +539,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
@@ -526,12 +562,14 @@
 }
 
 fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
-  let coord1 = (coord >> vec2<u32>(1));
+  let clampedCoords = min(vec2<u32>(coord), params.displayVisibleRectMax);
+  let plane0_clamped = vec2<u32>(round((params.loadTransformationMatrix * vec3<f32>(vec2<f32>(clampedCoords), 1))));
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
-    color = textureLoad(plane0, coord, 0).rgba;
+    color = textureLoad(plane0, plane0_clamped, 0).rgba;
   } else {
-    color = vec4<f32>((vec4<f32>(textureLoad(plane0, coord, 0).r, textureLoad(plane1, coord1, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
+    let plane1_clamped = vec2<u32>((vec2<f32>(plane0_clamped) * params.plane1CoordFactor));
+    color = vec4<f32>((vec4<f32>(textureLoad(plane0, plane0_clamped, 0).r, textureLoad(plane1, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
     color = vec4<f32>(gammaCorrection(color.rgb, params.gammaDecodeParams), color.a);
@@ -542,12 +580,14 @@
 }
 
 fn textureLoadExternal_1(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<u32>, params : ExternalTextureParams) -> vec4<f32> {
-  let coord1 = (coord >> vec2<u32>(1));
+  let clampedCoords = min(vec2<u32>(coord), params.displayVisibleRectMax);
+  let plane0_clamped = vec2<u32>(round((params.loadTransformationMatrix * vec3<f32>(vec2<f32>(clampedCoords), 1))));
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
-    color = textureLoad(plane0, coord, 0).rgba;
+    color = textureLoad(plane0, plane0_clamped, 0).rgba;
   } else {
-    color = vec4<f32>((vec4<f32>(textureLoad(plane0, coord, 0).r, textureLoad(plane1, coord1, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
+    let plane1_clamped = vec2<u32>((vec2<f32>(plane0_clamped) * params.plane1CoordFactor));
+    color = vec4<f32>((vec4<f32>(textureLoad(plane0, plane0_clamped, 0).r, textureLoad(plane1, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
     color = vec4<f32>(gammaCorrection(color.rgb, params.gammaDecodeParams), color.a);
@@ -605,6 +645,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(1) var ext_tex_plane_1 : texture_2d<f32>;
@@ -619,12 +666,14 @@
 }
 
 fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
-  let coord1 = (coord >> vec2<u32>(1));
+  let clampedCoords = min(vec2<u32>(coord), params.displayVisibleRectMax);
+  let plane0_clamped = vec2<u32>(round((params.loadTransformationMatrix * vec3<f32>(vec2<f32>(clampedCoords), 1))));
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
-    color = textureLoad(plane0, coord, 0).rgba;
+    color = textureLoad(plane0, plane0_clamped, 0).rgba;
   } else {
-    color = vec4<f32>((vec4<f32>(textureLoad(plane0, coord, 0).r, textureLoad(plane1, coord1, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
+    let plane1_clamped = vec2<u32>((vec2<f32>(plane0_clamped) * params.plane1CoordFactor));
+    color = vec4<f32>((vec4<f32>(textureLoad(plane0, plane0_clamped, 0).r, textureLoad(plane1, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
     color = vec4<f32>(gammaCorrection(color.rgb, params.gammaDecodeParams), color.a);
@@ -635,12 +684,14 @@
 }
 
 fn textureLoadExternal_1(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<u32>, params : ExternalTextureParams) -> vec4<f32> {
-  let coord1 = (coord >> vec2<u32>(1));
+  let clampedCoords = min(vec2<u32>(coord), params.displayVisibleRectMax);
+  let plane0_clamped = vec2<u32>(round((params.loadTransformationMatrix * vec3<f32>(vec2<f32>(clampedCoords), 1))));
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
-    color = textureLoad(plane0, coord, 0).rgba;
+    color = textureLoad(plane0, plane0_clamped, 0).rgba;
   } else {
-    color = vec4<f32>((vec4<f32>(textureLoad(plane0, coord, 0).r, textureLoad(plane1, coord1, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
+    let plane1_clamped = vec2<u32>((vec2<f32>(plane0_clamped) * params.plane1CoordFactor));
+    color = vec4<f32>((vec4<f32>(textureLoad(plane0, plane0_clamped, 0).r, textureLoad(plane1, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
     color = vec4<f32>(gammaCorrection(color.rgb, params.gammaDecodeParams), color.a);
@@ -699,6 +750,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -718,16 +776,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -739,12 +793,14 @@
 }
 
 fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
-  let coord1 = (coord >> vec2<u32>(1));
+  let clampedCoords = min(vec2<u32>(coord), params.displayVisibleRectMax);
+  let plane0_clamped = vec2<u32>(round((params.loadTransformationMatrix * vec3<f32>(vec2<f32>(clampedCoords), 1))));
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
-    color = textureLoad(plane0, coord, 0).rgba;
+    color = textureLoad(plane0, plane0_clamped, 0).rgba;
   } else {
-    color = vec4<f32>((vec4<f32>(textureLoad(plane0, coord, 0).r, textureLoad(plane1, coord1, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
+    let plane1_clamped = vec2<u32>((vec2<f32>(plane0_clamped) * params.plane1CoordFactor));
+    color = vec4<f32>((vec4<f32>(textureLoad(plane0, plane0_clamped, 0).r, textureLoad(plane1, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
     color = vec4<f32>(gammaCorrection(color.rgb, params.gammaDecodeParams), color.a);
@@ -799,6 +855,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -814,16 +877,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -835,12 +894,14 @@
 }
 
 fn textureLoadExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, coord : vec2<i32>, params : ExternalTextureParams) -> vec4<f32> {
-  let coord1 = (coord >> vec2<u32>(1));
+  let clampedCoords = min(vec2<u32>(coord), params.displayVisibleRectMax);
+  let plane0_clamped = vec2<u32>(round((params.loadTransformationMatrix * vec3<f32>(vec2<f32>(clampedCoords), 1))));
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
-    color = textureLoad(plane0, coord, 0).rgba;
+    color = textureLoad(plane0, plane0_clamped, 0).rgba;
   } else {
-    color = vec4<f32>((vec4<f32>(textureLoad(plane0, coord, 0).r, textureLoad(plane1, coord1, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
+    let plane1_clamped = vec2<u32>((vec2<f32>(plane0_clamped) * params.plane1CoordFactor));
+    color = vec4<f32>((vec4<f32>(textureLoad(plane0, plane0_clamped, 0).r, textureLoad(plane1, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
     color = vec4<f32>(gammaCorrection(color.rgb, params.gammaDecodeParams), color.a);
@@ -905,6 +966,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(4) var ext_tex_plane_1 : texture_2d<f32>;
@@ -942,16 +1010,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -1016,6 +1080,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1031,16 +1102,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -1109,6 +1176,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1129,16 +1203,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -1202,6 +1272,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1217,16 +1294,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -1297,6 +1370,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(3) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1316,16 +1396,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -1401,6 +1477,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(3) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1425,16 +1508,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -1506,6 +1585,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1521,16 +1607,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -1607,6 +1689,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1622,16 +1711,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -1696,6 +1781,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 fn f(ext_tex : texture_2d<f32>, ext_tex_plane_1 : texture_2d<f32>, ext_tex_params : ExternalTextureParams) -> vec2<u32> {
@@ -1748,6 +1840,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1765,16 +1864,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
@@ -1844,6 +1939,13 @@
   gammaEncodeParams : GammaTransferParams,
   gamutConversionMatrix : mat3x3<f32>,
   coordTransformationMatrix : mat3x2<f32>,
+  loadTransformationMatrix : mat3x2<f32>,
+  samplePlane0RectMin : vec2<f32>,
+  samplePlane0RectMax : vec2<f32>,
+  samplePlane1RectMin : vec2<f32>,
+  samplePlane1RectMax : vec2<f32>,
+  displayVisibleRectMax : vec2<u32>,
+  plane1CoordFactor : vec2<f32>,
 }
 
 @group(0) @binding(2) var ext_tex_plane_1 : texture_2d<f32>;
@@ -1864,16 +1966,12 @@
 
 fn textureSampleExternal(plane0 : texture_2d<f32>, plane1 : texture_2d<f32>, smp : sampler, coord : vec2<f32>, params : ExternalTextureParams) -> vec4<f32> {
   let modifiedCoords = (params.coordTransformationMatrix * vec3<f32>(coord, 1));
-  let plane0_dims = vec2<f32>(textureDimensions(plane0, 0));
-  let plane0_half_texel = (vec2<f32>(0.5) / plane0_dims);
-  let plane0_clamped = clamp(modifiedCoords, plane0_half_texel, (1 - plane0_half_texel));
-  let plane1_dims = vec2<f32>(textureDimensions(plane1, 0));
-  let plane1_half_texel = (vec2<f32>(0.5) / plane1_dims);
-  let plane1_clamped = clamp(modifiedCoords, plane1_half_texel, (1 - plane1_half_texel));
+  let plane0_clamped = clamp(modifiedCoords, params.samplePlane0RectMin, params.samplePlane0RectMax);
   var color : vec4<f32>;
   if ((params.numPlanes == 1)) {
     color = textureSampleLevel(plane0, smp, plane0_clamped, 0).rgba;
   } else {
+    let plane1_clamped = clamp(modifiedCoords, params.samplePlane1RectMin, params.samplePlane1RectMax);
     color = vec4<f32>((vec4<f32>(textureSampleLevel(plane0, smp, plane0_clamped, 0).r, textureSampleLevel(plane1, smp, plane1_clamped, 0).rg, 1) * params.yuvToRgbConversionMatrix), 1);
   }
   if ((params.doYuvToRgbConversionOnly == 0)) {
diff --git a/src/tint/lang/wgsl/ast/transform/push_constant_helper.cc b/src/tint/lang/wgsl/ast/transform/push_constant_helper.cc
index ba10cf4..6b72964 100644
--- a/src/tint/lang/wgsl/ast/transform/push_constant_helper.cc
+++ b/src/tint/lang/wgsl/ast/transform/push_constant_helper.cc
@@ -64,8 +64,7 @@
 void PushConstantHelper::InsertMember(const char* name, ast::Type type, uint32_t offset) {
     auto& member = member_map[offset];
     if (TINT_UNLIKELY(member != nullptr)) {
-        ctx.dst->Diagnostics().AddError(diag::System::Transform, Source{})
-            << "struct member offset collision";
+        ctx.dst->Diagnostics().AddError(Source{}) << "struct member offset collision";
     }
     member = ctx.dst->Member(name, type, Vector{ctx.dst->MemberOffset(core::AInt(offset))});
 }
diff --git a/src/tint/lang/wgsl/ast/transform/robustness.cc b/src/tint/lang/wgsl/ast/transform/robustness.cc
index 42273c5..fa00dc9 100644
--- a/src/tint/lang/wgsl/ast/transform/robustness.cc
+++ b/src/tint/lang/wgsl/ast/transform/robustness.cc
@@ -272,8 +272,7 @@
                 }
                 // Note: Don't be tempted to use the array override variable as an expression here,
                 // the name might be shadowed!
-                b.Diagnostics().AddError(diag::System::Transform, Source{})
-                    << core::type::Array::kErrExpectedConstantCount;
+                b.Diagnostics().AddError(Source{}) << core::type::Array::kErrExpectedConstantCount;
                 return nullptr;
             },  //
             TINT_ICE_ON_NO_MATCH);
diff --git a/src/tint/lang/wgsl/ast/transform/single_entry_point.cc b/src/tint/lang/wgsl/ast/transform/single_entry_point.cc
index a0952eb..ed16c78 100644
--- a/src/tint/lang/wgsl/ast/transform/single_entry_point.cc
+++ b/src/tint/lang/wgsl/ast/transform/single_entry_point.cc
@@ -55,8 +55,7 @@
 
     auto* cfg = inputs.Get<Config>();
     if (cfg == nullptr) {
-        b.Diagnostics().AddError(diag::System::Transform, Source{})
-            << "missing transform data for " << TypeInfo().name;
+        b.Diagnostics().AddError(Source{}) << "missing transform data for " << TypeInfo().name;
         return resolver::Resolve(b);
     }
 
@@ -72,7 +71,7 @@
         }
     }
     if (entry_point == nullptr) {
-        b.Diagnostics().AddError(diag::System::Transform, Source{})
+        b.Diagnostics().AddError(Source{})
             << "entry point '" << cfg->entry_point_name << "' not found";
         return resolver::Resolve(b);
     }
diff --git a/src/tint/lang/wgsl/ast/transform/substitute_override.cc b/src/tint/lang/wgsl/ast/transform/substitute_override.cc
index 4711b36..2c5e06a 100644
--- a/src/tint/lang/wgsl/ast/transform/substitute_override.cc
+++ b/src/tint/lang/wgsl/ast/transform/substitute_override.cc
@@ -71,8 +71,7 @@
 
     const auto* data = config.Get<Config>();
     if (!data) {
-        b.Diagnostics().AddError(diag::System::Transform, Source{})
-            << "Missing override substitution data";
+        b.Diagnostics().AddError(Source{}) << "Missing override substitution data";
         return resolver::Resolve(b);
     }
 
@@ -91,7 +90,7 @@
         auto iter = data->map.find(sem->Attributes().override_id.value());
         if (iter == data->map.end()) {
             if (!w->initializer) {
-                b.Diagnostics().AddError(diag::System::Transform, Source{})
+                b.Diagnostics().AddError(Source{})
                     << "Initializer not provided for override, and override not overridden.";
                 return nullptr;
             }
@@ -108,8 +107,7 @@
             [&](const core::type::F16*) { return b.Expr(f16(value)); });
 
         if (!ctor) {
-            b.Diagnostics().AddError(diag::System::Transform, Source{})
-                << "Failed to create override-expression";
+            b.Diagnostics().AddError(Source{}) << "Failed to create override-expression";
             return nullptr;
         }
 
diff --git a/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc b/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
index 8eae5a6..4c2145d 100644
--- a/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
+++ b/src/tint/lang/wgsl/ast/transform/vertex_pulling.cc
@@ -260,7 +260,7 @@
         for (auto* fn : src.AST().Functions()) {
             if (fn->PipelineStage() == PipelineStage::kVertex) {
                 if (func != nullptr) {
-                    b.Diagnostics().AddError(diag::System::Transform, Source{})
+                    b.Diagnostics().AddError(Source{})
                         << "VertexPulling found more than one vertex entry point";
                     return resolver::Resolve(b);
                 }
@@ -268,8 +268,7 @@
             }
         }
         if (func == nullptr) {
-            b.Diagnostics().AddError(diag::System::Transform, Source{})
-                << "Vertex stage entry point not found";
+            b.Diagnostics().AddError(Source{}) << "Vertex stage entry point not found";
             return resolver::Resolve(b);
         }
 
@@ -357,7 +356,7 @@
             const VertexBufferLayoutDescriptor& buffer_layout = cfg.vertex_state[buffer_idx];
 
             if ((buffer_layout.array_stride & 3) != 0) {
-                b.Diagnostics().AddError(diag::System::Transform, Source{})
+                b.Diagnostics().AddError(Source{})
                     << "WebGPU requires that vertex stride must be a multiple of 4 bytes, "
                        "but VertexPulling array stride for buffer "
                     << buffer_idx << " was " << buffer_layout.array_stride << " bytes";
@@ -395,7 +394,7 @@
 
                 // Base types must match between the vertex stream and the WGSL variable
                 if (!IsTypeCompatible(var_dt, fmt_dt)) {
-                    b.Diagnostics().AddError(diag::System::Transform, Source{})
+                    b.Diagnostics().AddError(Source{})
                         << "VertexAttributeDescriptor for location "
                         << attribute_desc.shader_location << " has format " << attribute_desc.format
                         << " but shader expects " << var.type->FriendlyName();
diff --git a/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.cc b/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.cc
index 7745eb3..8807cb4 100644
--- a/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.cc
+++ b/src/tint/lang/wgsl/ast/transform/zero_init_workgroup_memory.cc
@@ -385,7 +385,7 @@
                 //      `(idx % modulo) / division`
                 auto count = arr->ConstantCount();
                 if (!count) {
-                    ctx.dst->Diagnostics().AddError(diag::System::Transform, Source{})
+                    ctx.dst->Diagnostics().AddError(Source{})
                         << core::type::Array::kErrExpectedConstantCount;
                     return Expression{};  // error
                 }
diff --git a/src/tint/lang/wgsl/diagnostic_severity.cc b/src/tint/lang/wgsl/diagnostic_severity.cc
index d47944d..4fd7c55 100644
--- a/src/tint/lang/wgsl/diagnostic_severity.cc
+++ b/src/tint/lang/wgsl/diagnostic_severity.cc
@@ -49,7 +49,7 @@
         case DiagnosticSeverity::kInfo:
             return diag::Severity::Note;
         default:
-            return diag::Severity::InternalCompilerError;
+            return diag::Severity::Error;
     }
 }
 
diff --git a/src/tint/lang/wgsl/diagnostic_severity.cc.tmpl b/src/tint/lang/wgsl/diagnostic_severity.cc.tmpl
index fdc3a8d..ff0f70e 100644
--- a/src/tint/lang/wgsl/diagnostic_severity.cc.tmpl
+++ b/src/tint/lang/wgsl/diagnostic_severity.cc.tmpl
@@ -26,7 +26,7 @@
         case DiagnosticSeverity::kInfo:
             return diag::Severity::Note;
         default:
-            return diag::Severity::InternalCompilerError;
+            return diag::Severity::Error;
     }
 }
 
diff --git a/src/tint/lang/wgsl/helpers/check_supported_extensions.cc b/src/tint/lang/wgsl/helpers/check_supported_extensions.cc
index 5a4372c..647221e 100644
--- a/src/tint/lang/wgsl/helpers/check_supported_extensions.cc
+++ b/src/tint/lang/wgsl/helpers/check_supported_extensions.cc
@@ -48,9 +48,8 @@
     for (auto* enable : module.Enables()) {
         for (auto* ext : enable->extensions) {
             if (!set.Contains(ext->name)) {
-                diags.AddError(diag::System::Writer, ext->source)
-                    << writer_name << " backend does not support extension "
-                    << style::Code(ext->name);
+                diags.AddError(ext->source) << writer_name << " backend does not support extension "
+                                            << style::Code(ext->name);
                 return false;
             }
         }
diff --git a/src/tint/lang/wgsl/helpers/ir_program_test.h b/src/tint/lang/wgsl/helpers/ir_program_test.h
index 6a71008..5468ebc 100644
--- a/src/tint/lang/wgsl/helpers/ir_program_test.h
+++ b/src/tint/lang/wgsl/helpers/ir_program_test.h
@@ -35,7 +35,7 @@
 #include <utility>
 
 #include "gtest/gtest.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/core/number.h"
 #include "src/tint/lang/wgsl/program/program_builder.h"
diff --git a/src/tint/lang/wgsl/inspector/inspector.cc b/src/tint/lang/wgsl/inspector/inspector.cc
index 1cdbcac..34700c8 100644
--- a/src/tint/lang/wgsl/inspector/inspector.cc
+++ b/src/tint/lang/wgsl/inspector/inspector.cc
@@ -571,13 +571,12 @@
 const ast::Function* Inspector::FindEntryPointByName(const std::string& name) {
     auto* func = program_.AST().Functions().Find(program_.Symbols().Get(name));
     if (!func) {
-        diagnostics_.AddError(diag::System::Inspector, Source{}) << name << " was not found!";
+        diagnostics_.AddError(Source{}) << name << " was not found!";
         return nullptr;
     }
 
     if (!func->IsEntryPoint()) {
-        diagnostics_.AddError(diag::System::Inspector, Source{})
-            << name << " is not an entry point!";
+        diagnostics_.AddError(Source{}) << name << " is not an entry point!";
         return nullptr;
     }
 
@@ -1029,17 +1028,6 @@
 
     std::unordered_set<BindingPoint> seen = {};
 
-    auto sample_type_for_call_and_type = [](wgsl::BuiltinFn builtin) {
-        if (builtin == wgsl::BuiltinFn::kTextureNumLevels ||
-            builtin == wgsl::BuiltinFn::kTextureDimensions ||
-            builtin == wgsl::BuiltinFn::kTextureLoad) {
-            return TextureQueryType::kTextureNumLevels;
-        }
-
-        TINT_ASSERT(builtin == wgsl::BuiltinFn::kTextureNumSamples);
-        return TextureQueryType::kTextureNumSamples;
-    };
-
     Hashmap<const sem::Function*, Hashmap<const ast::Parameter*, TextureQueryType, 4>, 8>
         fn_to_data;
 
@@ -1090,35 +1078,62 @@
             tint::Switch(
                 call->Target(),
                 [&](const sem::BuiltinFn* builtin) {
-                    if (builtin->Fn() != wgsl::BuiltinFn::kTextureNumLevels &&
-                        builtin->Fn() != wgsl::BuiltinFn::kTextureNumSamples &&
-                        builtin->Fn() != wgsl::BuiltinFn::kTextureLoad &&
-                        // When textureDimension takes level as the input,
-                        // it requires calls to textureNumLevels to clamp mip levels.
-                        !(builtin->Fn() == wgsl::BuiltinFn::kTextureDimensions &&
-                          call->Declaration()->args.Length() > 1)) {
-                        return;
-                    }
+                    auto queryTextureBuiltin = [&](TextureQueryType type,
+                                                   const sem::Call* builtin_call,
+                                                   const sem::Variable* texture_sem = nullptr) {
+                        TINT_ASSERT(builtin_call);
+                        if (!texture_sem) {
+                            auto* texture_expr = builtin_call->Declaration()->args[0];
+                            texture_sem = sem.GetVal(texture_expr)->RootIdentifier();
+                        }
+                        tint::Switch(
+                            texture_sem,  //
+                            [&](const sem::GlobalVariable* global) {
+                                save_if_needed(global, type);
+                            },
+                            [&](const sem::Parameter* param) {
+                                record_function_param(fn, param->Declaration(), type);
+                            },
+                            TINT_ICE_ON_NO_MATCH);
+                    };
 
-                    auto* texture_expr = call->Declaration()->args[0];
-                    auto* texture_sem = sem.GetVal(texture_expr)->RootIdentifier();
-                    TINT_ASSERT(texture_sem);
-                    if (builtin->Fn() == wgsl::BuiltinFn::kTextureLoad &&
-                        texture_sem->Type()
-                            ->UnwrapRef()
-                            ->IsAnyOf<core::type::MultisampledTexture,
-                                      core::type::DepthMultisampledTexture>()) {
-                        return;
+                    switch (builtin->Fn()) {
+                        case wgsl::BuiltinFn::kTextureNumLevels: {
+                            queryTextureBuiltin(TextureQueryType::kTextureNumLevels, call);
+                            break;
+                        }
+                        case wgsl::BuiltinFn::kTextureDimensions: {
+                            if (call->Declaration()->args.Length() <= 1) {
+                                // When textureDimension only takes a texture as the input,
+                                // it doesn't require calls to textureNumLevels to clamp mip levels.
+                                return;
+                            }
+                            queryTextureBuiltin(TextureQueryType::kTextureNumLevels, call);
+                            break;
+                        }
+                        case wgsl::BuiltinFn::kTextureLoad: {
+                            auto* texture_expr = call->Declaration()->args[0];
+                            auto* texture_sem = sem.GetVal(texture_expr)->RootIdentifier();
+                            TINT_ASSERT(texture_sem);
+                            if (texture_sem->Type()
+                                    ->UnwrapRef()
+                                    ->IsAnyOf<core::type::MultisampledTexture,
+                                              core::type::DepthMultisampledTexture>()) {
+                                // When textureLoad takes a multisampled texture as the input,
+                                // it doesn't require to query the mip level.
+                                return;
+                            }
+                            queryTextureBuiltin(TextureQueryType::kTextureNumLevels, call,
+                                                texture_sem);
+                            break;
+                        }
+                        case wgsl::BuiltinFn::kTextureNumSamples: {
+                            queryTextureBuiltin(TextureQueryType::kTextureNumSamples, call);
+                            break;
+                        }
+                        default:
+                            return;
                     }
-
-                    auto type = sample_type_for_call_and_type(builtin->Fn());
-                    tint::Switch(
-                        texture_sem,  //
-                        [&](const sem::GlobalVariable* global) { save_if_needed(global, type); },
-                        [&](const sem::Parameter* param) {
-                            record_function_param(fn, param->Declaration(), type);
-                        },
-                        TINT_ICE_ON_NO_MATCH);
                 },
                 [&](const sem::Function* func) {
                     // A function call, check to see if any params needed to be tracked back to a
diff --git a/src/tint/lang/wgsl/ir_roundtrip_fuzz.cc b/src/tint/lang/wgsl/ir_roundtrip_fuzz.cc
index 0265906..47af44a 100644
--- a/src/tint/lang/wgsl/ir_roundtrip_fuzz.cc
+++ b/src/tint/lang/wgsl/ir_roundtrip_fuzz.cc
@@ -30,7 +30,7 @@
 #include <iostream>
 
 #include "src/tint/cmd/fuzz/ir/fuzz.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/wgsl/reader/lower/lower.h"
 #include "src/tint/lang/wgsl/reader/parser/parser.h"
 #include "src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.h"
diff --git a/src/tint/lang/wgsl/ir_roundtrip_test.cc b/src/tint/lang/wgsl/ir_roundtrip_test.cc
index ac07621..39c7ca9 100644
--- a/src/tint/lang/wgsl/ir_roundtrip_test.cc
+++ b/src/tint/lang/wgsl/ir_roundtrip_test.cc
@@ -29,7 +29,7 @@
 
 #include "gtest/gtest.h"
 
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.h"
 #include "src/tint/lang/wgsl/reader/reader.h"
 #include "src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.h"
diff --git a/src/tint/lang/wgsl/ls/diagnostics.cc b/src/tint/lang/wgsl/ls/diagnostics.cc
index 58ed542..98ff03c 100644
--- a/src/tint/lang/wgsl/ls/diagnostics.cc
+++ b/src/tint/lang/wgsl/ls/diagnostics.cc
@@ -49,8 +49,6 @@
                 d.severity = lsp::DiagnosticSeverity::kWarning;
                 break;
             case diag::Severity::Error:
-            case diag::Severity::InternalCompilerError:
-            case diag::Severity::Fatal:
                 d.severity = lsp::DiagnosticSeverity::kError;
                 break;
         }
diff --git a/src/tint/lang/wgsl/program/program.cc b/src/tint/lang/wgsl/program/program.cc
index c649781..c1dfffb 100644
--- a/src/tint/lang/wgsl/program/program.cc
+++ b/src/tint/lang/wgsl/program/program.cc
@@ -81,7 +81,7 @@
         // 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_.AddError(diag::System::Program, Source{}) << "invalid program generated";
+        diagnostics_.AddError(Source{}) << "invalid program generated";
     }
 }
 
diff --git a/src/tint/lang/wgsl/program/program_test.cc b/src/tint/lang/wgsl/program/program_test.cc
index 2572ca5..d5f2bb3 100644
--- a/src/tint/lang/wgsl/program/program_test.cc
+++ b/src/tint/lang/wgsl/program/program_test.cc
@@ -93,7 +93,7 @@
 }
 
 TEST_F(ProgramTest, DiagnosticsMove) {
-    Diagnostics().AddError(diag::System::Program, Source{}) << "an error message";
+    Diagnostics().AddError(Source{}) << "an error message";
 
     Program program_a(std::move(*this));
     EXPECT_FALSE(program_a.IsValid());
diff --git a/src/tint/lang/wgsl/reader/parser/parser.cc b/src/tint/lang/wgsl/reader/parser/parser.cc
index e9eee00..3eddee4 100644
--- a/src/tint/lang/wgsl/reader/parser/parser.cc
+++ b/src/tint/lang/wgsl/reader/parser/parser.cc
@@ -244,28 +244,27 @@
 
 Parser::Failure::Errored Parser::AddError(const Source& source, std::string_view err) {
     if (silence_diags_ == 0) {
-        builder_.Diagnostics().AddError(diag::System::Reader, source) << err;
+        builder_.Diagnostics().AddError(source) << err;
     }
     return Failure::kErrored;
 }
 
 Parser::Failure::Errored Parser::AddError(const Source& source, StyledText&& err) {
     if (silence_diags_ == 0) {
-        builder_.Diagnostics().AddError(diag::System::Reader, source) << std::move(err);
+        builder_.Diagnostics().AddError(source) << std::move(err);
     }
     return Failure::kErrored;
 }
 
 void Parser::AddNote(const Source& source, std::string_view err) {
     if (silence_diags_ == 0) {
-        builder_.Diagnostics().AddNote(diag::System::Reader, source) << err;
+        builder_.Diagnostics().AddNote(source) << err;
     }
 }
 
 void Parser::deprecated(const Source& source, std::string_view msg) {
     if (silence_diags_ == 0) {
-        builder_.Diagnostics().AddWarning(diag::System::Reader, source)
-            << "use of deprecated language feature: " << msg;
+        builder_.Diagnostics().AddWarning(source) << "use of deprecated language feature: " << msg;
     }
 }
 
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
index 4feffb8..9933ed1 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
@@ -200,9 +200,7 @@
         ~ControlStackScope() { impl_->control_stack_.Pop(); }
     };
 
-    diag::Diagnostic& AddError(const Source& source) {
-        return diagnostics_.AddError(tint::diag::System::IR, source);
-    }
+    diag::Diagnostic& AddError(const Source& source) { return diagnostics_.AddError(source); }
 
     bool NeedTerminator() { return current_block_ && !current_block_->Terminator(); }
 
@@ -1353,7 +1351,7 @@
     auto r = b.Build();
     if (r != Success) {
         diag::List err = std::move(r.Failure().reason);
-        err.AddNote(diag::System::IR, Source{}) << "AST:\n" + Program::printer(program);
+        err.AddNote(Source{}) << "AST:\n" + Program::printer(program);
         return Failure{err};
     }
 
diff --git a/src/tint/lang/wgsl/reader/reader.cc b/src/tint/lang/wgsl/reader/reader.cc
index f2e20be..ed8009a 100644
--- a/src/tint/lang/wgsl/reader/reader.cc
+++ b/src/tint/lang/wgsl/reader/reader.cc
@@ -41,8 +41,7 @@
     if (TINT_UNLIKELY(file->content.data.size() >
                       static_cast<size_t>(std::numeric_limits<uint32_t>::max()))) {
         ProgramBuilder b;
-        b.Diagnostics().AddError(tint::diag::System::Reader, Source{})
-            << "WGSL source must be 0xffffffff bytes or fewer";
+        b.Diagnostics().AddError(tint::Source{}) << "WGSL source must be 0xffffffff bytes or fewer";
         return Program(std::move(b));
     }
     Parser parser(file);
diff --git a/src/tint/lang/wgsl/resolver/dependency_graph.cc b/src/tint/lang/wgsl/resolver/dependency_graph.cc
index 7e2db61..36f97ba 100644
--- a/src/tint/lang/wgsl/resolver/dependency_graph.cc
+++ b/src/tint/lang/wgsl/resolver/dependency_graph.cc
@@ -133,12 +133,12 @@
 
 /// @returns a new error diagnostic with the given source.
 diag::Diagnostic& AddError(diag::List& diagnostics, const Source& source) {
-    return diagnostics.AddError(diag::System::Resolver, source);
+    return diagnostics.AddError(source);
 }
 
 /// @returns a new note diagnostic with the given source.
 diag::Diagnostic& AddNote(diag::List& diagnostics, const Source& source) {
-    return diagnostics.AddNote(diag::System::Resolver, source);
+    return diagnostics.AddNote(source);
 }
 
 /// DependencyScanner is used to traverse a module to build the list of
diff --git a/src/tint/lang/wgsl/resolver/function_validation_test.cc b/src/tint/lang/wgsl/resolver/function_validation_test.cc
index 764af82..0abe53b 100644
--- a/src/tint/lang/wgsl/resolver/function_validation_test.cc
+++ b/src/tint/lang/wgsl/resolver/function_validation_test.cc
@@ -255,12 +255,12 @@
     auto* var = Var("a", ty.i32(), Expr(2_i));
 
     Func(Source{{12, 34}}, "func", tint::Empty, ty.i32(),
-         Vector{
-             Decl(var),
-         });
+         Block(Source{Source::Range{{45, 56}, {78, 90}}}, Vector{
+                                                              Decl(var),
+                                                          }));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), R"(12:34 error: missing return at end of function)");
+    EXPECT_EQ(r()->error(), R"(78:89 error: missing return at end of function)");
 }
 
 TEST_F(ResolverFunctionValidationTest, VoidFunctionEndWithoutReturnStatementEmptyBody_Pass) {
@@ -274,10 +274,11 @@
 TEST_F(ResolverFunctionValidationTest, FunctionEndWithoutReturnStatementEmptyBody_Fail) {
     // fn func() -> int {}
 
-    Func(Source{{12, 34}}, "func", tint::Empty, ty.i32(), tint::Empty);
+    Func(Source{{12, 34}}, "func", tint::Empty, ty.i32(),
+         Block(Source{Source::Range{{45, 56}, {78, 90}}}, tint::Empty));
 
     EXPECT_FALSE(r()->Resolve());
-    EXPECT_EQ(r()->error(), R"(12:34 error: missing return at end of function)");
+    EXPECT_EQ(r()->error(), R"(78:89 error: missing return at end of function)");
 }
 
 TEST_F(ResolverFunctionValidationTest, FunctionTypeMustMatchReturnStatementType_Pass) {
@@ -1022,7 +1023,7 @@
     for (int i = 0; i < 256; i++) {
         params.Push(Param("param_" + std::to_string(i), ty.i32()));
     }
-    Func(Source{{12, 34}}, "f", params, ty.void_(), tint::Empty);
+    Func(Ident(Source{{12, 34}}, "f"), params, ty.void_(), tint::Empty);
 
     EXPECT_FALSE(r()->Resolve());
     EXPECT_EQ(r()->error(), R"(12:34 error: function declares 256 parameters, maximum is 255)");
diff --git a/src/tint/lang/wgsl/resolver/resolver.cc b/src/tint/lang/wgsl/resolver/resolver.cc
index b1a1775..fad6159 100644
--- a/src/tint/lang/wgsl/resolver/resolver.cc
+++ b/src/tint/lang/wgsl/resolver/resolver.cc
@@ -1156,9 +1156,10 @@
     }
 
     if (auto* str = return_type->As<core::type::Struct>()) {
-        if (!ApplyAddressSpaceUsageToType(core::AddressSpace::kUndefined, str, decl->source)) {
-            AddNote(decl->source) << "while instantiating return type for "
-                                  << decl->name->symbol.NameView();
+        if (!ApplyAddressSpaceUsageToType(core::AddressSpace::kUndefined, str,
+                                          decl->return_type->source)) {
+            AddNote(decl->return_type->source)
+                << "while instantiating return type for " << decl->name->symbol.NameView();
             return nullptr;
         }
 
@@ -5048,22 +5049,21 @@
         TINT_ICE() << msg;
     }
     diag::Diagnostic err{};
-    err.severity = diag::Severity::InternalCompilerError;
-    err.system = diag::System::Resolver;
+    err.severity = diag::Severity::Error;
     err.source = source;
     diagnostics_.Add(std::move(err)) << msg;
 }
 
 diag::Diagnostic& Resolver::AddError(const Source& source) const {
-    return diagnostics_.AddError(diag::System::Resolver, source);
+    return diagnostics_.AddError(source);
 }
 
 diag::Diagnostic& Resolver::AddWarning(const Source& source) const {
-    return diagnostics_.AddWarning(diag::System::Resolver, source);
+    return diagnostics_.AddWarning(source);
 }
 
 diag::Diagnostic& Resolver::AddNote(const Source& source) const {
-    return diagnostics_.AddNote(diag::System::Resolver, source);
+    return diagnostics_.AddNote(source);
 }
 
 }  // namespace tint::resolver
diff --git a/src/tint/lang/wgsl/resolver/sem_helper.cc b/src/tint/lang/wgsl/resolver/sem_helper.cc
index fbd74a9..1fe3934 100644
--- a/src/tint/lang/wgsl/resolver/sem_helper.cc
+++ b/src/tint/lang/wgsl/resolver/sem_helper.cc
@@ -228,14 +228,14 @@
 }
 
 diag::Diagnostic& SemHelper::AddError(const Source& source) const {
-    return builder_->Diagnostics().AddError(diag::System::Resolver, source);
+    return builder_->Diagnostics().AddError(source);
 }
 
 diag::Diagnostic& SemHelper::AddWarning(const Source& source) const {
-    return builder_->Diagnostics().AddWarning(diag::System::Resolver, source);
+    return builder_->Diagnostics().AddWarning(source);
 }
 
 diag::Diagnostic& SemHelper::AddNote(const Source& source) const {
-    return builder_->Diagnostics().AddNote(diag::System::Resolver, source);
+    return builder_->Diagnostics().AddNote(source);
 }
 }  // namespace tint::resolver
diff --git a/src/tint/lang/wgsl/resolver/uniformity.cc b/src/tint/lang/wgsl/resolver/uniformity.cc
index 818e5e1..776d424 100644
--- a/src/tint/lang/wgsl/resolver/uniformity.cc
+++ b/src/tint/lang/wgsl/resolver/uniformity.cc
@@ -130,6 +130,7 @@
         kFunctionCallArgumentContents,
         kFunctionCallPointerArgumentResult,
         kFunctionCallReturnValue,
+        kFunctionPointerParameterContents,
     };
 
     /// The type of the node.
@@ -214,13 +215,15 @@
             parameters[i].sem = sem;
 
             parameters[i].value = CreateNode({"param_", param_name});
+            parameters[i].value->ast = param;
             if (sem->Type()->Is<core::type::Pointer>()) {
                 // Create extra nodes for a pointer parameter's initial contents and its contents
                 // when the function returns.
                 parameters[i].ptr_input_contents =
-                    CreateNode({"ptrparam_", param_name, "_input_contents"});
+                    CreateNode({"ptrparam_", param_name, "_input_contents"}, param);
                 parameters[i].ptr_output_contents =
                     CreateNode({"ptrparam_", param_name, "_output_contents"});
+                parameters[i].ptr_input_contents->type = Node::kFunctionPointerParameterContents;
                 variables.Set(sem, parameters[i].ptr_input_contents);
                 local_var_decls.Add(sem);
             } else {
@@ -1822,14 +1825,17 @@
         Traverse(required_to_be_uniform);
 
         // Get the source of the non-uniform value.
-        auto* non_uniform_source = may_be_non_uniform->visited_from;
+        auto* non_uniform_source = may_be_non_uniform;
+        if (non_uniform_source == function.may_be_non_uniform) {
+            non_uniform_source = non_uniform_source->visited_from;
+        }
         TINT_ASSERT(non_uniform_source);
 
         // Show where the non-uniform value results in non-uniform control flow.
         auto* control_flow = TraceBackAlongPathUntil(
             non_uniform_source, [](Node* node) { return node->affects_control_flow; });
         if (control_flow) {
-            diagnostics_.AddNote(diag::System::Resolver, control_flow->ast->source)
+            diagnostics_.AddNote(control_flow->ast->source)
                 << "control flow depends on possibly non-uniform value";
             // TODO(jrprice): There are cases where the function with uniformity requirements is not
             // actually inside this control flow construct, for example:
@@ -1859,11 +1865,10 @@
                     return "";
             }
         };
-        auto param_type = [&](const sem::Parameter* param) {
-            if (ast::HasAttribute<ast::BuiltinAttribute>(param->Declaration()->attributes)) {
+        auto param_type = [&](const ast::Parameter* param) {
+            if (ast::HasAttribute<ast::BuiltinAttribute>(param->attributes)) {
                 return "builtin ";
-            } else if (ast::HasAttribute<ast::LocationAttribute>(
-                           param->Declaration()->attributes)) {
+            } else if (ast::HasAttribute<ast::LocationAttribute>(param->attributes)) {
                 return "user-defined input ";
             } else {
                 return "parameter ";
@@ -1877,18 +1882,31 @@
                 auto* var = sem_.GetVal(ident)->UnwrapLoad()->As<sem::VariableUser>()->Variable();
                 if (auto* param = var->As<sem::Parameter>()) {
                     auto* func = param->Owner()->As<sem::Function>();
-                    diagnostics_.AddNote(diag::System::Resolver, ident->source)
-                        << param_type(param) << "'" << NameFor(ident) << "' of '" << NameFor(func)
-                        << "' may be non-uniform";
+                    diagnostics_.AddNote(ident->source)
+                        << param_type(param->Declaration()) << "'" << NameFor(ident) << "' of '"
+                        << NameFor(func) << "' may be non-uniform";
                 } else {
-                    diagnostics_.AddNote(diag::System::Resolver, ident->source)
+                    diagnostics_.AddNote(ident->source)
                         << "reading from " << var_type(var) << "'" << NameFor(ident)
                         << "' may result in a non-uniform value";
                 }
             },
+            [&](const ast::Parameter* p) {
+                auto* param = sem_.Get(p);
+                auto* func = param->Owner()->As<sem::Function>();
+                if (non_uniform_source->type == Node::kFunctionPointerParameterContents) {
+                    diagnostics_.AddNote(p->source)
+                        << "parameter '" << NameFor(p) << "' of '" << NameFor(func)
+                        << "' may point to a non-uniform value";
+                } else {
+                    diagnostics_.AddNote(p->source)
+                        << param_type(p) << "'" << NameFor(p) << "' of '" << NameFor(func)
+                        << "' may be non-uniform";
+                }
+            },
             [&](const ast::Variable* v) {
                 auto* var = sem_.Get(v);
-                diagnostics_.AddNote(diag::System::Resolver, v->source)
+                diagnostics_.AddNote(v->source)
                     << "reading from " << var_type(var) << "'" << NameFor(v)
                     << "' may result in a non-uniform value";
             },
@@ -1896,14 +1914,14 @@
                 auto target_name = NameFor(c->target);
                 switch (non_uniform_source->type) {
                     case Node::kFunctionCallReturnValue: {
-                        diagnostics_.AddNote(diag::System::Resolver, c->source)
+                        diagnostics_.AddNote(c->source)
                             << "return value of '" + target_name + "' may be non-uniform";
                         break;
                     }
                     case Node::kFunctionCallArgumentContents: {
                         auto* arg = c->args[non_uniform_source->arg_index];
                         auto* var = sem_.GetVal(arg)->RootIdentifier();
-                        diagnostics_.AddNote(diag::System::Resolver, var->Declaration()->source)
+                        diagnostics_.AddNote(var->Declaration()->source)
                             << "reading from " << var_type(var) << "'" << NameFor(var)
                             << "' may result in a non-uniform value";
                         break;
@@ -1911,14 +1929,13 @@
                     case Node::kFunctionCallArgumentValue: {
                         auto* arg = c->args[non_uniform_source->arg_index];
                         // TODO(jrprice): Which output? (return value vs another pointer argument).
-                        diagnostics_.AddNote(diag::System::Resolver, arg->source)
+                        diagnostics_.AddNote(arg->source)
                             << "passing non-uniform pointer to '" << target_name
                             << "' may produce a non-uniform output";
                         break;
                     }
                     case Node::kFunctionCallPointerArgumentResult: {
-                        diagnostics_.AddNote(diag::System::Resolver,
-                                             c->args[non_uniform_source->arg_index]->source)
+                        diagnostics_.AddNote(c->args[non_uniform_source->arg_index]->source)
                             << "contents of pointer may become non-uniform after calling '"
                             << target_name << "'";
                         break;
@@ -1930,8 +1947,7 @@
                 }
             },
             [&](const ast::Expression* e) {
-                diagnostics_.AddNote(diag::System::Resolver, e->source)
-                    << "result of expression may be non-uniform";
+                diagnostics_.AddNote(e->source) << "result of expression may be non-uniform";
             },  //
             TINT_ICE_ON_NO_MATCH);
     }
@@ -1945,7 +1961,6 @@
         auto report = [&](Source source, std::string msg, bool note) {
             diag::Diagnostic error{};
             error.severity = note ? diag::Severity::Note : wgsl::ToSeverity(severity);
-            error.system = diag::System::Resolver;
             error.source = source;
             error.message = msg;
             diagnostics_.Add(std::move(error));
diff --git a/src/tint/lang/wgsl/resolver/uniformity_test.cc b/src/tint/lang/wgsl/resolver/uniformity_test.cc
index c4a5000..9c15fa6 100644
--- a/src/tint/lang/wgsl/resolver/uniformity_test.cc
+++ b/src/tint/lang/wgsl/resolver/uniformity_test.cc
@@ -433,9 +433,9 @@
   if (i == 0) {
   ^^
 
-test:5:7 note: parameter 'i' of 'foo' may be non-uniform
-  if (i == 0) {
-      ^
+test:4:8 note: parameter 'i' of 'foo' may be non-uniform
+fn foo(i : i32) {
+       ^
 
 test:11:7 note: possibly non-uniform value passed here
   foo(rw);
@@ -3744,8 +3744,8 @@
   if (*p == 0) {
   ^^
 
-test:5:8 note: parameter 'p' of 'bar' may be non-uniform
-  if (*p == 0) {
+test:4:8 note: parameter 'p' of 'bar' may point to a non-uniform value
+fn bar(p : ptr<function, i32>) {
        ^
 
 test:12:7 note: possibly non-uniform value passed via pointer here
@@ -3875,8 +3875,8 @@
   if (*p == 0) {
   ^^
 
-test:5:8 note: parameter 'p' of 'bar' may be non-uniform
-  if (*p == 0) {
+test:4:8 note: parameter 'p' of 'bar' may point to a non-uniform value
+fn bar(p : ptr<function, i32>) {
        ^
 
 test:13:7 note: possibly non-uniform value passed via pointer here
@@ -3916,8 +3916,8 @@
   if (*p == 0) {
   ^^
 
-test:5:8 note: parameter 'p' of 'bar' may be non-uniform
-  if (*p == 0) {
+test:4:8 note: parameter 'p' of 'bar' may point to a non-uniform value
+fn bar(p : ptr<function, i32>) {
        ^
 
 test:12:7 note: possibly non-uniform value passed via pointer here
@@ -4074,8 +4074,8 @@
   if (*p == 0) {
   ^^
 
-test:5:8 note: parameter 'p' of 'zoo' may be non-uniform
-  if (*p == 0) {
+test:4:8 note: parameter 'p' of 'zoo' may point to a non-uniform value
+fn zoo(p : ptr<function, i32>) {
        ^
 
 test:11:7 note: possibly non-uniform value passed via pointer here
@@ -4162,8 +4162,8 @@
   if (*p == 0) {
   ^^
 
-test:6:8 note: parameter 'p' of 'zoo' may be non-uniform
-  if (*p == 0) {
+test:5:8 note: parameter 'p' of 'zoo' may point to a non-uniform value
+fn zoo(p : ptr<function, i32>) {
        ^
 
 test:12:7 note: possibly non-uniform value passed via pointer here
@@ -8032,8 +8032,8 @@
   if (*p == 0) {
   ^^
 
-test:7:8 note: parameter 'p' of 'bar' may be non-uniform
-  if (*p == 0) {
+test:6:8 note: parameter 'p' of 'bar' may point to a non-uniform value
+fn bar(p : ptr<function, i32>) -> i32 {
        ^
 
 test:15:9 note: possibly non-uniform value passed via pointer here
@@ -8108,8 +8108,8 @@
   if (*p == 0) {
   ^^
 
-test:7:8 note: parameter 'p' of 'bar' may be non-uniform
-  if (*p == 0) {
+test:6:8 note: parameter 'p' of 'bar' may point to a non-uniform value
+fn bar(p : ptr<function, i32>) -> i32 {
        ^
 
 test:15:9 note: possibly non-uniform value passed via pointer here
@@ -8181,9 +8181,9 @@
   if (*p == 0) {
   ^^
 
-test:10:8 note: parameter 'p' of 'b' may be non-uniform
-  if (*p == 0) {
-       ^
+test:9:6 note: parameter 'p' of 'b' may point to a non-uniform value
+fn b(p : ptr<function, i32>) -> i32 {
+     ^
 
 test:19:22 note: possibly non-uniform value passed via pointer here
   arr[a(&i)] = arr[b(&i)];
@@ -8222,9 +8222,9 @@
   if (cond == 0) {
   ^^
 
-test:5:7 note: parameter 'cond' of 'bar' may be non-uniform
-  if (cond == 0) {
-      ^^^^
+test:4:8 note: parameter 'cond' of 'bar' may be non-uniform
+fn bar(cond : i32) -> i32 {
+       ^^^^
 
 test:13:11 note: possibly non-uniform value passed here
   arr[bar(non_uniform)] = 0;
@@ -8263,9 +8263,9 @@
   if (cond == 0) {
   ^^
 
-test:5:7 note: parameter 'cond' of 'bar' may be non-uniform
-  if (cond == 0) {
-      ^^^^
+test:4:8 note: parameter 'cond' of 'bar' may be non-uniform
+fn bar(cond : i32) -> i32 {
+       ^^^^
 
 test:13:14 note: possibly non-uniform value passed here
   *&(arr[bar(non_uniform)]) = 0;
@@ -8304,9 +8304,9 @@
   if (cond == 0) {
   ^^
 
-test:5:7 note: parameter 'cond' of 'bar' may be non-uniform
-  if (cond == 0) {
-      ^^^^
+test:4:8 note: parameter 'cond' of 'bar' may be non-uniform
+fn bar(cond : i32) -> i32 {
+       ^^^^
 
 test:13:14 note: possibly non-uniform value passed here
   (&arr)[bar(non_uniform)] = 0;
@@ -8345,9 +8345,9 @@
   if (cond == 0) {
   ^^
 
-test:5:7 note: parameter 'cond' of 'bar' may be non-uniform
-  if (cond == 0) {
-      ^^^^
+test:4:8 note: parameter 'cond' of 'bar' may be non-uniform
+fn bar(cond : i32) -> i32 {
+       ^^^^
 
 test:13:14 note: possibly non-uniform value passed here
   (&(arr[bar(non_uniform)])).y = 0;
@@ -8418,9 +8418,9 @@
   if (*p == 0) {
   ^^
 
-test:10:8 note: parameter 'p' of 'b' may be non-uniform
-  if (*p == 0) {
-       ^
+test:9:6 note: parameter 'p' of 'b' may point to a non-uniform value
+fn b(p : ptr<function, i32>) -> i32 {
+     ^
 
 test:19:23 note: possibly non-uniform value passed via pointer here
   arr[a(&i)] += arr[b(&i)];
@@ -9988,9 +9988,9 @@
   if (a == 42) {
   ^^
 
-test:5:7 note: parameter 'a' of 'zoo' may be non-uniform
-  if (a == 42) {
-      ^
+test:4:8 note: parameter 'a' of 'zoo' may be non-uniform
+fn zoo(a : i32) {
+       ^
 
 test:11:7 note: possibly non-uniform value passed here
   zoo(b);
@@ -10096,5 +10096,52 @@
 )");
 }
 
+TEST_F(UniformityAnalysisTest, Error_PointerParameterContentsRequiresUniformity_AfterControlFlow) {
+    // Test that we can find the correct source of uniformity inside a function called with a
+    // pointer parameter, when the pointer contents is used after control flow that introduces extra
+    // nodes for merging the pointer contents.
+    std::string src = R"(
+var<private> non_uniform : i32;
+
+fn foo(p : ptr<function, i32>) {
+  for (var i = 0; i < 3; i++) {
+    continue;
+  }
+  if (*p == 0) {
+    return;
+  }
+  _ = dpdx(1.0);
+}
+
+fn main() {
+  var f = non_uniform;
+  foo(&f);
+}
+)";
+
+    RunTest(src, false);
+    EXPECT_EQ(error_,
+              R"(test:11:7 error: 'dpdx' must only be called from uniform control flow
+  _ = dpdx(1.0);
+      ^^^^^^^^^
+
+test:8:3 note: control flow depends on possibly non-uniform value
+  if (*p == 0) {
+  ^^
+
+test:4:8 note: parameter 'p' of 'foo' may point to a non-uniform value
+fn foo(p : ptr<function, i32>) {
+       ^
+
+test:16:7 note: possibly non-uniform value passed via pointer here
+  foo(&f);
+      ^^
+
+test:15:11 note: reading from module-scope private variable 'non_uniform' may result in a non-uniform value
+  var f = non_uniform;
+          ^^^^^^^^^^^
+)");
+}
+
 }  // namespace
 }  // namespace tint::resolver
diff --git a/src/tint/lang/wgsl/resolver/validator.cc b/src/tint/lang/wgsl/resolver/validator.cc
index 1c110f8..7963482 100644
--- a/src/tint/lang/wgsl/resolver/validator.cc
+++ b/src/tint/lang/wgsl/resolver/validator.cc
@@ -180,15 +180,15 @@
 Validator::~Validator() = default;
 
 diag::Diagnostic& Validator::AddError(const Source& source) const {
-    return diagnostics_.AddError(diag::System::Resolver, source);
+    return diagnostics_.AddError(source);
 }
 
 diag::Diagnostic& Validator::AddWarning(const Source& source) const {
-    return diagnostics_.AddWarning(diag::System::Resolver, source);
+    return diagnostics_.AddWarning(source);
 }
 
 diag::Diagnostic& Validator::AddNote(const Source& source) const {
-    return diagnostics_.AddNote(diag::System::Resolver, source);
+    return diagnostics_.AddNote(source);
 }
 
 diag::Diagnostic* Validator::MaybeAddDiagnostic(wgsl::DiagnosticRule rule,
@@ -197,7 +197,6 @@
     if (severity != wgsl::DiagnosticSeverity::kOff) {
         diag::Diagnostic d{};
         d.severity = ToSeverity(severity);
-        d.system = diag::System::Resolver;
         d.source = source;
         return &diagnostics_.Add(std::move(d));
     }
@@ -1097,8 +1096,8 @@
     }
 
     if (decl->params.Length() > kMaxFunctionParameters) {
-        AddError(decl->source) << "function declares " << decl->params.Length()
-                               << " parameters, maximum is " << kMaxFunctionParameters;
+        AddError(decl->name->source) << "function declares " << decl->params.Length()
+                                     << " parameters, maximum is " << kMaxFunctionParameters;
         return false;
     }
 
@@ -1115,7 +1114,9 @@
                 behaviors = sem_.Get(last)->Behaviors();
             }
             if (behaviors.Contains(sem::Behavior::kNext)) {
-                AddError(decl->source) << "missing return at end of function";
+                auto end_source = decl->body->source.End();
+                end_source.range.begin.column--;
+                AddError(end_source) << "missing return at end of function";
                 return false;
             }
         } else if (TINT_UNLIKELY(IsValidationEnabled(
diff --git a/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc b/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc
index 8ad212a..710c70a 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/wgsl/writer/ast_printer/ast_printer.cc
@@ -338,7 +338,7 @@
 void ASTPrinter::EmitImageFormat(StringStream& out, const core::TexelFormat fmt) {
     switch (fmt) {
         case core::TexelFormat::kUndefined:
-            diagnostics_.AddError(diag::System::Writer, Source{}) << "unknown image format";
+            diagnostics_.AddError(Source{}) << "unknown image format";
             break;
         default:
             out << fmt;
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
index 68d4db1..5e601cc 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
@@ -32,7 +32,7 @@
 
 #include "src/tint/lang/core/access.h"
 #include "src/tint/lang/core/address_space.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/core/texel_format.h"
 #include "src/tint/lang/core/type/storage_texture.h"
 #include "src/tint/lang/core/type/texture_dimension.h"
diff --git a/src/tint/lang/wgsl/writer/raise/ptr_to_ref_test.cc b/src/tint/lang/wgsl/writer/raise/ptr_to_ref_test.cc
index 3862749..40f02f4 100644
--- a/src/tint/lang/wgsl/writer/raise/ptr_to_ref_test.cc
+++ b/src/tint/lang/wgsl/writer/raise/ptr_to_ref_test.cc
@@ -32,7 +32,7 @@
 
 #include "gtest/gtest.h"
 #include "src/tint/lang/core/ir/builder.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/core/ir/validator.h"
 
 namespace tint::wgsl::writer::raise {
@@ -65,10 +65,7 @@
     }
 
     /// @returns the transformed module as a disassembled string
-    std::string str() {
-        core::ir::Disassembler dis(mod);
-        return "\n" + dis.Disassemble().Plain();
-    }
+    std::string str() { return "\n" + core::ir::Disassemble(mod).Plain(); }
 
   protected:
     /// The test IR module.
diff --git a/src/tint/lang/wgsl/writer/raise/rename_conflicts_test.cc b/src/tint/lang/wgsl/writer/raise/rename_conflicts_test.cc
index 7be25fc..d5a25db 100644
--- a/src/tint/lang/wgsl/writer/raise/rename_conflicts_test.cc
+++ b/src/tint/lang/wgsl/writer/raise/rename_conflicts_test.cc
@@ -32,7 +32,7 @@
 
 #include "gtest/gtest.h"
 #include "src/tint/lang/core/ir/builder.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/core/type/matrix.h"
 
@@ -65,10 +65,7 @@
     }
 
     /// @returns the transformed module as a disassembled string
-    std::string str() {
-        core::ir::Disassembler dis(mod);
-        return "\n" + dis.Disassemble().Plain();
-    }
+    std::string str() { return "\n" + core::ir::Disassemble(mod).Plain(); }
 
   protected:
     /// The test IR module.
diff --git a/src/tint/lang/wgsl/writer/writer_test.cc b/src/tint/lang/wgsl/writer/writer_test.cc
index ca2f339..d9e573c 100644
--- a/src/tint/lang/wgsl/writer/writer_test.cc
+++ b/src/tint/lang/wgsl/writer/writer_test.cc
@@ -32,7 +32,7 @@
 #include <string_view>
 
 #include "gtest/gtest.h"
-#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/ir/disassembly.h"
 #include "src/tint/lang/core/ir/ir_helper_test.h"
 #include "src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.h"
 #include "src/tint/lang/wgsl/writer/ir_to_program/program_options.h"
diff --git a/src/tint/utils/command/command_posix.cc b/src/tint/utils/command/command_posix.cc
index 61bb0b1..f3c30de 100644
--- a/src/tint/utils/command/command_posix.cc
+++ b/src/tint/utils/command/command_posix.cc
@@ -29,6 +29,7 @@
 
 #include "src/tint/utils/command/command.h"
 
+#include <limits.h>
 #include <sys/poll.h>
 #include <sys/stat.h>
 #include <sys/wait.h>
@@ -119,7 +120,19 @@
     return s.st_mode & S_IXUSR;
 }
 
+std::string GetCWD() {
+    char cwd[PATH_MAX] = "";
+    [[maybe_unused]] auto res = getcwd(cwd, sizeof(cwd));
+    return cwd;
+}
+
 std::string FindExecutable(const std::string& name) {
+    if (name.length() >= 1 && name[0] != '/') {
+        auto in_cwd = GetCWD() + "/" + name;
+        if (ExecutableExists(in_cwd)) {
+            return in_cwd;
+        }
+    }
     if (ExecutableExists(name)) {
         return name;
     }
diff --git a/src/tint/utils/command/command_windows.cc b/src/tint/utils/command/command_windows.cc
index 2916d9a..a6a577b 100644
--- a/src/tint/utils/command/command_windows.cc
+++ b/src/tint/utils/command/command_windows.cc
@@ -145,7 +145,20 @@
     return false;
 }
 
+std::string GetCWD() {
+    char cwd[MAX_PATH] = "";
+    GetCurrentDirectoryA(sizeof(cwd), cwd);
+    return cwd;
+}
+
 std::string FindExecutable(const std::string& name) {
+    auto in_cwd = GetCWD() + "/" + name;
+    if (ExecutableExists(in_cwd)) {
+        return in_cwd;
+    }
+    if (ExecutableExists(in_cwd + ".exe")) {
+        return in_cwd + ".exe";
+    }
     if (ExecutableExists(name)) {
         return name;
     }
diff --git a/src/tint/utils/diagnostic/diagnostic.h b/src/tint/utils/diagnostic/diagnostic.h
index 69f77d2..6471b5a 100644
--- a/src/tint/utils/diagnostic/diagnostic.h
+++ b/src/tint/utils/diagnostic/diagnostic.h
@@ -28,8 +28,8 @@
 #ifndef SRC_TINT_UTILS_DIAGNOSTIC_DIAGNOSTIC_H_
 #define SRC_TINT_UTILS_DIAGNOSTIC_DIAGNOSTIC_H_
 
+#include <cstdint>
 #include <memory>
-#include <ostream>
 #include <string>
 #include <utility>
 
@@ -41,36 +41,13 @@
 namespace tint::diag {
 
 /// Severity is an enumerator of diagnostic severities.
-enum class Severity { Note, Warning, Error, InternalCompilerError, Fatal };
+enum class Severity : uint8_t { Note, Warning, Error };
 
 /// @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,
-    Builtin,
-    Clone,
-    Constant,
-    Inspector,
-    Intrinsics,
-    IR,
-    Program,
-    ProgramBuilder,
-    Reader,
-    Resolver,
-    Semantic,
-    Symbol,
-    Test,
-    Transform,
-    Type,
-    Utils,
-    Writer,
-    Unknown,
-};
-
 /// Diagnostic holds all the information for a single compiler diagnostic
 /// message.
 class Diagnostic {
@@ -99,8 +76,6 @@
     Source source;
     /// message is the text associated with the diagnostic.
     StyledText message;
-    /// system is the Tint system that raised the diagnostic.
-    System system;
     /// 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).
@@ -178,61 +153,38 @@
     }
 
     /// Adds the note message with the given Source to the end of this list.
-    /// @param system the system raising the note message
     /// @param source the source of the note diagnostic
     /// @returns a reference to the new diagnostic.
     /// @note The returned reference must not be used after the list is mutated again.
-    diag::Diagnostic& AddNote(System system, const Source& source) {
+    diag::Diagnostic& AddNote(const Source& source) {
         diag::Diagnostic note{};
         note.severity = diag::Severity::Note;
-        note.system = system;
         note.source = source;
         return 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 source the source of the warning diagnostic
     /// @returns a reference to the new diagnostic.
     /// @note The returned reference must not be used after the list is mutated again.
-    diag::Diagnostic& AddWarning(System system, const Source& source) {
+    diag::Diagnostic& AddWarning(const Source& source) {
         diag::Diagnostic warning{};
         warning.severity = diag::Severity::Warning;
-        warning.system = system;
         warning.source = source;
         return Add(std::move(warning));
     }
 
     /// Adds the error message with the given Source to the end of this list.
-    /// @param system the system raising the error message
     /// @param source the source of the error diagnostic
     /// @returns a reference to the new diagnostic.
     /// @note The returned reference must not be used after the list is mutated again.
-    diag::Diagnostic& AddError(System system, const Source& source) {
+    diag::Diagnostic& AddError(const Source& source) {
         diag::Diagnostic error{};
         error.severity = diag::Severity::Error;
-        error.system = system;
         error.source = source;
         return 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 source the source of the internal compiler error
-    /// @param file the Source::File owned by this diagnostic
-    /// @returns a reference to the new diagnostic.
-    /// @note The returned reference must not be used after the list is mutated again.
-    diag::Diagnostic& AddIce(System system,
-                             const Source& source,
-                             std::shared_ptr<Source::File> file) {
-        diag::Diagnostic ice{};
-        ice.severity = diag::Severity::InternalCompilerError;
-        ice.system = system;
-        ice.source = source;
-        ice.owned_file = std::move(file);
-        return Add(std::move(ice));
-    }
-
     /// @returns true iff the diagnostic list contains errors diagnostics (or of
     /// higher severity).
     bool ContainsErrors() const { return error_count_ > 0; }
diff --git a/src/tint/utils/diagnostic/diagnostic_test.cc b/src/tint/utils/diagnostic/diagnostic_test.cc
index 3d76f1e..9a00566 100644
--- a/src/tint/utils/diagnostic/diagnostic_test.cc
+++ b/src/tint/utils/diagnostic/diagnostic_test.cc
@@ -36,7 +36,7 @@
 TEST(DiagListTest, CtorInitializerList) {
     Diagnostic err_a, err_b;
     err_a.severity = Severity::Error;
-    err_b.severity = Severity::Fatal;
+    err_b.severity = Severity::Warning;
     List list{err_a, err_b};
     EXPECT_EQ(list.Count(), 2u);
 }
@@ -44,7 +44,7 @@
 TEST(DiagListTest, CtorVectorRef) {
     Diagnostic err_a, err_b;
     err_a.severity = Severity::Error;
-    err_b.severity = Severity::Fatal;
+    err_b.severity = Severity::Warning;
     List list{Vector{err_a, err_b}};
     EXPECT_EQ(list.Count(), 2u);
 }
diff --git a/src/tint/utils/diagnostic/formatter.cc b/src/tint/utils/diagnostic/formatter.cc
index 9fd279b..5b5c574 100644
--- a/src/tint/utils/diagnostic/formatter.cc
+++ b/src/tint/utils/diagnostic/formatter.cc
@@ -50,10 +50,6 @@
             return "warning";
         case Severity::Error:
             return "error";
-        case Severity::InternalCompilerError:
-            return "internal compiler error";
-        case Severity::Fatal:
-            return "fatal";
     }
     return "";
 }
@@ -127,10 +123,6 @@
             case Severity::Error:
                 style = style::Error + style::Bold;
                 break;
-            case Severity::Fatal:
-            case Severity::InternalCompilerError:
-                style = style::Fatal + style::Bold;
-                break;
         }
         prefix.Push(TextAndStyle{ToString(diag.severity), style});
     }
diff --git a/src/tint/utils/diagnostic/formatter_test.cc b/src/tint/utils/diagnostic/formatter_test.cc
index 6d77367..9fee7c9 100644
--- a/src/tint/utils/diagnostic/formatter_test.cc
+++ b/src/tint/utils/diagnostic/formatter_test.cc
@@ -36,12 +36,11 @@
 namespace tint::diag {
 namespace {
 
-Diagnostic Diag(Severity severity, Source source, std::string message, System system) {
+Diagnostic Diag(Severity severity, Source source, std::string message) {
     Diagnostic d;
     d.severity = severity;
     d.source = source;
     d.message = std::move(message);
-    d.system = system;
     return d;
 }
 
@@ -62,47 +61,19 @@
   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);
-    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 ascii_diag_note =
+        Diag(Severity::Note, Source{Source::Range{Source::Location{1, 14}}, &ascii_file}, "purr");
+    Diagnostic ascii_diag_warn =
+        Diag(Severity::Warning, Source{Source::Range{{2, 14}, {2, 18}}, &ascii_file}, "grrr");
+    Diagnostic ascii_diag_err =
+        Diag(Severity::Error, Source{Source::Range{{3, 16}, {3, 21}}, &ascii_file}, "hiss");
 
-    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);
-    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);
+    Diagnostic utf8_diag_note =
+        Diag(Severity::Note, Source{Source::Range{Source::Location{1, 15}}, &utf8_file}, "purr");
+    Diagnostic utf8_diag_warn =
+        Diag(Severity::Warning, Source{Source::Range{{2, 15}, {2, 19}}, &utf8_file}, "grrr");
+    Diagnostic utf8_diag_err =
+        Diag(Severity::Error, Source{Source::Range{{3, 15}, {3, 20}}, &utf8_file}, "hiss");
 };
 
 TEST_F(DiagFormatterTest, Simple) {
@@ -126,7 +97,7 @@
 
 TEST_F(DiagFormatterTest, SimpleNoSource) {
     Formatter fmt{{false, false, false, false}};
-    auto diag = Diag(Severity::Note, Source{}, "no source!", System::Test);
+    auto diag = Diag(Severity::Note, Source{}, "no source!");
     auto got = fmt.Format(List{diag}).Plain();
     auto* expect = "no source!";
     ASSERT_EQ(expect, got);
@@ -202,8 +173,8 @@
 }
 
 TEST_F(DiagFormatterTest, BasicWithMultiLine) {
-    auto multiline = Diag(Severity::Warning, Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file},
-                          "multiline", System::Test);
+    auto multiline =
+        Diag(Severity::Warning, Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file}, "multiline");
     Formatter fmt{{false, false, true, false}};
     auto got = fmt.Format(List{multiline}).Plain();
     auto* expect = R"(2:9: multiline
@@ -218,8 +189,8 @@
 }
 
 TEST_F(DiagFormatterTest, UnicodeWithMultiLine) {
-    auto multiline = Diag(Severity::Warning, Source{Source::Range{{2, 9}, {4, 15}}, &utf8_file},
-                          "multiline", System::Test);
+    auto multiline =
+        Diag(Severity::Warning, Source{Source::Range{{2, 9}, {4, 15}}, &utf8_file}, "multiline");
     Formatter fmt{{false, false, true, false}};
     auto got = fmt.Format(List{multiline}).Plain();
     auto* expect =
@@ -249,8 +220,8 @@
 }
 
 TEST_F(DiagFormatterTest, BasicWithMultiLineTab4) {
-    auto multiline = Diag(Severity::Warning, Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file},
-                          "multiline", System::Test);
+    auto multiline =
+        Diag(Severity::Warning, Source{Source::Range{{2, 9}, {4, 15}}, &ascii_file}, "multiline");
     Formatter fmt{{false, false, true, false, 4u}};
     auto got = fmt.Format(List{multiline}).Plain();
     auto* expect = R"(2:9: multiline
@@ -264,32 +235,10 @@
     ASSERT_EQ(expect, got);
 }
 
-TEST_F(DiagFormatterTest, ICE) {
-    Formatter fmt{{}};
-    auto got = fmt.Format(List{ascii_diag_ice}).Plain();
-    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}).Plain();
-    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.AddError(System::Test, Source{{{10, 20}, {30, 20}}, &ascii_file}) << "oob";
+    list.AddError(Source{{{10, 20}, {30, 20}}, &ascii_file}) << "oob";
     auto got = fmt.Format(list).Plain();
     auto* expect = R"(file.name:10:20 error: oob
 
diff --git a/src/tint/utils/macros/compiler.h b/src/tint/utils/macros/compiler.h
index a3de76f..a62a59e 100644
--- a/src/tint/utils/macros/compiler.h
+++ b/src/tint/utils/macros/compiler.h
@@ -54,6 +54,10 @@
 #define TINT_DISABLE_WARNING_ZERO_AS_NULLPTR             /* currently no-op */
 #define TINT_DISABLE_WARNING_MISSING_DESTRUCTOR_OVERRIDE /* currently no-op */
 
+#define TINT_BEGIN_DISABLE_ALL_WARNINGS() __pragma(warning(push, 0)) TINT_REQUIRE_SEMICOLON
+
+#define TINT_END_DISABLE_ALL_WARNINGS() __pragma(warning(pop)) TINT_REQUIRE_SEMICOLON
+
 // clang-format off
 #define TINT_BEGIN_DISABLE_WARNING(name)     \
     __pragma(warning(push))                  \
@@ -71,6 +75,10 @@
 #define TINT_UNLIKELY(x) x /* currently no-op */
 #define TINT_LIKELY(x) x   /* currently no-op */
 
+#if defined(__SANITIZE_ADDRESS__)
+#define TINT_ASAN_ENABLED
+#endif
+
 #elif defined(__clang__)
 ////////////////////////////////////////////////////////////////////////////////
 // Clang
@@ -123,6 +131,15 @@
     _Pragma("clang diagnostic pop")          \
     TINT_REQUIRE_SEMICOLON
 
+#define TINT_BEGIN_DISABLE_ALL_WARNINGS() \
+    _Pragma("clang diagnostic push")      \
+    _Pragma("clang diagnostic ignored \"-Weverything\"")       \
+    TINT_REQUIRE_SEMICOLON
+
+#define TINT_END_DISABLE_ALL_WARNINGS() \
+    _Pragma("clang diagnostic pop")     \
+    TINT_REQUIRE_SEMICOLON
+
 #define TINT_BEGIN_DISABLE_WARNING(name)     \
     _Pragma("clang diagnostic push")         \
     TINT_CONCAT(TINT_DISABLE_WARNING_, name) \
@@ -135,6 +152,11 @@
 
 #define TINT_UNLIKELY(x) __builtin_expect(!!(x), false)
 #define TINT_LIKELY(x) __builtin_expect(!!(x), true)
+
+#if __has_feature(address_sanitizer)
+#define TINT_ASAN_ENABLED
+#endif
+
 #elif defined(__GNUC__)
 ////////////////////////////////////////////////////////////////////////////////
 // GCC
@@ -164,6 +186,31 @@
 #define TINT_END_DISABLE_PROTOBUF_WARNINGS() _Pragma("GCC diagnostic pop") TINT_REQUIRE_SEMICOLON
 
 // clang-format off
+#define TINT_BEGIN_DISABLE_ALL_WARNINGS()             \
+    _Pragma("GCC diagnostic push")                    \
+    TINT_DISABLE_WARNING_CONSTANT_OVERFLOW            \
+    TINT_DISABLE_WARNING_MAYBE_UNINITIALIZED          \
+    TINT_DISABLE_WARNING_NEWLINE_EOF                  \
+    TINT_DISABLE_WARNING_OLD_STYLE_CAST               \
+    TINT_DISABLE_WARNING_SIGN_CONVERSION              \
+    TINT_DISABLE_WARNING_UNREACHABLE_CODE             \
+    TINT_DISABLE_WARNING_WEAK_VTABLES                 \
+    TINT_DISABLE_WARNING_FLOAT_EQUAL                  \
+    TINT_DISABLE_WARNING_DEPRECATED                   \
+    TINT_DISABLE_WARNING_RESERVED_IDENTIFIER          \
+    TINT_DISABLE_WARNING_RESERVED_MACRO_IDENTIFIER    \
+    TINT_DISABLE_WARNING_UNUSED_VALUE                 \
+    TINT_DISABLE_WARNING_UNUSED_PARAMETER             \
+    TINT_DISABLE_WARNING_SHADOW_FIELD_IN_CONSTRUCTOR  \
+    TINT_DISABLE_WARNING_EXTRA_SEMICOLON              \
+    TINT_DISABLE_WARNING_ZERO_AS_NULLPTR              \
+    TINT_DISABLE_WARNING_MISSING_DESTRUCTOR_OVERRIDE  \
+    TINT_REQUIRE_SEMICOLON
+// clang-format on
+
+#define TINT_END_DISABLE_ALL_WARNINGS() _Pragma("GCC diagnostic pop") TINT_REQUIRE_SEMICOLON
+
+// clang-format off
 #define TINT_BEGIN_DISABLE_WARNING(name)     \
     _Pragma("GCC diagnostic push")           \
     TINT_CONCAT(TINT_DISABLE_WARNING_, name) \
@@ -175,10 +222,17 @@
 
 #define TINT_UNLIKELY(x) __builtin_expect(!!(x), false)
 #define TINT_LIKELY(x) __builtin_expect(!!(x), true)
+
+#if defined(__SANITIZE_ADDRESS__)
+#define TINT_ASAN_ENABLED
+#endif
+
 #else
 ////////////////////////////////////////////////////////////////////////////////
 // Other
 ////////////////////////////////////////////////////////////////////////////////
+#define TINT_BEGIN_DISABLE_ALL_WARNINGS() TINT_REQUIRE_SEMICOLON
+#define TINT_END_DISABLE_ALL_WARNINGS TINT_REQUIRE_SEMICOLON
 #define TINT_BEGIN_DISABLE_WARNING(name) TINT_REQUIRE_SEMICOLON
 #define TINT_END_DISABLE_WARNING(name) TINT_REQUIRE_SEMICOLON
 #define TINT_BEGIN_DISABLE_PROTOBUF_WARNINGS() TINT_REQUIRE_SEMICOLON
diff --git a/src/tint/utils/result/result.cc b/src/tint/utils/result/result.cc
index a3ecfc3..0a8c6c8 100644
--- a/src/tint/utils/result/result.cc
+++ b/src/tint/utils/result/result.cc
@@ -32,7 +32,7 @@
 Failure::Failure() = default;
 
 Failure::Failure(std::string_view err) {
-    reason.AddError(diag::System::Unknown, Source{}) << err;
+    reason.AddError(Source{}) << err;
 }
 
 Failure::Failure(diag::Diagnostic diagnostic) : reason(diag::List{std::move(diagnostic)}) {}