Import Tint changes from Dawn

Changes:
  - ca3c3f474ad7a599843ea0281c92a43b12ac9af9 Update ir::builder includes. by dan sinclair <dsinclair@chromium.org>
  - 1caf8c690ce75667a38d7fce4ab2886f8fc66b98 Cleanup some includes by dan sinclair <dsinclair@chromium.org>
  - 2320e63eb964badefc9166be1c07d0a516b87e63 Add check for option to tint::glsl::writer::ASTFuzzer by Ryan Harrison <rharrison@chromium.org>
  - 9f0b65f8a6eefb2b7960522c701946c60fa07489 Remove unused inferred template default parameter. by dan sinclair <dsinclair@chromium.org>
  - 7eb5f8c515572ac254338f18feae8dc1f47c69b7 [spirv] Fix terminator creation in MergeReturn by James Price <jrprice@google.com>
  - e93ab20875ca3303ab2a1bc543debdda83c755f6 [tint][wgsl][fuzz] Add more to dictionary.txt by Ben Clayton <bclayton@google.com>
  - 1b0c89597857ca23dbdd33f2b472b222915af08e [ir] Fix store target validation highlight by James Price <jrprice@google.com>
  - 77ac3f1ee9557561e80f8df2391381496a96d01f [ir] Add RemoveTerminatorArgs transform by James Price <jrprice@google.com>
  - 1ffa4e1ee10b3c2a81d64ecb3efc46b827874d2d [tintd] Allow all extensions and features by James Price <jrprice@google.com>
GitOrigin-RevId: ca3c3f474ad7a599843ea0281c92a43b12ac9af9
Change-Id: I2b9f8c7144fe38fe5c32b1b93139ed6c648f2ca6
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/191080
Commit-Queue: James Price <jrprice@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/cmd/fuzz/wgsl/dictionary.txt b/src/tint/cmd/fuzz/wgsl/dictionary.txt
index a9118b3..1adc488 100644
--- a/src/tint/cmd/fuzz/wgsl/dictionary.txt
+++ b/src/tint/cmd/fuzz/wgsl/dictionary.txt
@@ -57,7 +57,10 @@
 "/"
 "// AAAA"
 "// AAAB"
+"// ABBB"
 "// BAAA"
+"// BBBA"
+"// BBBB"
 "/="
 ":"
 ";"
@@ -215,6 +218,7 @@
 "fma"
 "fn"
 "for"
+"for (;true;) {}"
 "fract"
 "frag_depth"
 "fragment"
diff --git a/src/tint/cmd/fuzz/wgsl/dictionary.txt.tmpl b/src/tint/cmd/fuzz/wgsl/dictionary.txt.tmpl
index e5605d7..2655ada 100644
--- a/src/tint/cmd/fuzz/wgsl/dictionary.txt.tmpl
+++ b/src/tint/cmd/fuzz/wgsl/dictionary.txt.tmpl
@@ -108,8 +108,16 @@
 {{- /* =========================== Base64 comments =========================== */ -}}
 {{- $tokens = Append $tokens
     "// AAAA"
-    "// BAAA"
     "// AAAB"
+    "// ABBB"
+    "// BAAA"
+    "// BBBA"
+    "// BBBB"
+-}}
+
+{{- /* ========================= Interesting patterns ======================== */ -}}
+{{- $tokens = Append $tokens
+    "for (;true;) {}"
 -}}
 
 {{- /* ========================== Builtin functions ========================== */ -}}
diff --git a/src/tint/lang/core/fluent_types.h b/src/tint/lang/core/fluent_types.h
index 2d347a0..a67135a 100644
--- a/src/tint/lang/core/fluent_types.h
+++ b/src/tint/lang/core/fluent_types.h
@@ -50,7 +50,7 @@
 /// @tparam T the array element type
 /// @tparam N the array length. 0 represents a runtime-sized array.
 /// @see https://www.w3.org/TR/WGSL/#array-types
-template <typename T = Infer, uint32_t N = 0>
+template <typename T, uint32_t N = 0>
 struct array {
     /// the array element type
     using type = T;
@@ -70,7 +70,7 @@
 /// A 'fluent' type helper used to construct an ast::Vector or type::Vector.
 /// @tparam N the vector width
 /// @tparam T the vector element type
-template <uint32_t N, typename T = Infer>
+template <uint32_t N, typename T>
 struct vec {
     /// the vector width
     static constexpr uint32_t width = N;
@@ -83,7 +83,7 @@
 /// @tparam R the number of rows of the matrix
 /// @tparam T the matrix element type
 /// @see https://www.w3.org/TR/WGSL/#matrix-types
-template <uint32_t C, uint32_t R, typename T = Infer>
+template <uint32_t C, uint32_t R, typename T>
 struct mat {
     /// the number of columns of the matrix
     static constexpr uint32_t columns = C;
diff --git a/src/tint/lang/core/ir/builder.h b/src/tint/lang/core/ir/builder.h
index 48cad54..9e4f75e 100644
--- a/src/tint/lang/core/ir/builder.h
+++ b/src/tint/lang/core/ir/builder.h
@@ -30,9 +30,8 @@
 
 #include <utility>
 
-#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/constant/scalar.h"  // IWYU pragma: export
+#include "src/tint/lang/core/constant/splat.h"   // IWYU pragma: export
 #include "src/tint/lang/core/ir/access.h"
 #include "src/tint/lang/core/ir/bitcast.h"
 #include "src/tint/lang/core/ir/block_param.h"
@@ -69,16 +68,17 @@
 #include "src/tint/lang/core/ir/user_call.h"
 #include "src/tint/lang/core/ir/value.h"
 #include "src/tint/lang/core/ir/var.h"
-#include "src/tint/lang/core/type/array.h"
-#include "src/tint/lang/core/type/bool.h"
-#include "src/tint/lang/core/type/f16.h"
-#include "src/tint/lang/core/type/f32.h"
-#include "src/tint/lang/core/type/i32.h"
+#include "src/tint/lang/core/type/array.h"  // IWYU pragma: export
+#include "src/tint/lang/core/type/bool.h"   // IWYU pragma: export
+#include "src/tint/lang/core/type/f16.h"    // IWYU pragma: export
+#include "src/tint/lang/core/type/f32.h"    // IWYU pragma: export
+#include "src/tint/lang/core/type/i32.h"    // IWYU pragma: export
 #include "src/tint/lang/core/type/matrix.h"
-#include "src/tint/lang/core/type/pointer.h"
-#include "src/tint/lang/core/type/u32.h"
+#include "src/tint/lang/core/type/memory_view.h"
+#include "src/tint/lang/core/type/pointer.h"  // IWYU pragma: export
+#include "src/tint/lang/core/type/u32.h"      // IWYU pragma: export
 #include "src/tint/lang/core/type/vector.h"
-#include "src/tint/lang/core/type/void.h"
+#include "src/tint/lang/core/type/void.h"  // IWYU pragma: export
 #include "src/tint/utils/ice/ice.h"
 #include "src/tint/utils/macros/scoped_assignment.h"
 #include "src/tint/utils/rtti/switch.h"
diff --git a/src/tint/lang/core/ir/load_test.cc b/src/tint/lang/core/ir/load_test.cc
index 62977b3..5cf8a63 100644
--- a/src/tint/lang/core/ir/load_test.cc
+++ b/src/tint/lang/core/ir/load_test.cc
@@ -27,7 +27,6 @@
 
 #include "gmock/gmock.h"
 #include "src/tint/lang/core/ir/builder.h"
-#include "src/tint/lang/core/ir/instruction.h"
 #include "src/tint/lang/core/ir/ir_helper_test.h"
 
 namespace tint::core::ir {
diff --git a/src/tint/lang/core/ir/transform/BUILD.bazel b/src/tint/lang/core/ir/transform/BUILD.bazel
index 6444ea9..3bdcfad 100644
--- a/src/tint/lang/core/ir/transform/BUILD.bazel
+++ b/src/tint/lang/core/ir/transform/BUILD.bazel
@@ -52,6 +52,7 @@
     "direct_variable_access.cc",
     "multiplanar_external_texture.cc",
     "preserve_padding.cc",
+    "remove_terminator_args.cc",
     "robustness.cc",
     "shader_io.cc",
     "std140.cc",
@@ -73,6 +74,7 @@
     "direct_variable_access.h",
     "multiplanar_external_texture.h",
     "preserve_padding.h",
+    "remove_terminator_args.h",
     "robustness.h",
     "shader_io.h",
     "std140.h",
@@ -124,6 +126,7 @@
     "helper_test.h",
     "multiplanar_external_texture_test.cc",
     "preserve_padding_test.cc",
+    "remove_terminator_args_test.cc",
     "robustness_test.cc",
     "std140_test.cc",
     "value_to_let_test.cc",
diff --git a/src/tint/lang/core/ir/transform/BUILD.cmake b/src/tint/lang/core/ir/transform/BUILD.cmake
index d8c3431..6713300 100644
--- a/src/tint/lang/core/ir/transform/BUILD.cmake
+++ b/src/tint/lang/core/ir/transform/BUILD.cmake
@@ -67,6 +67,8 @@
   lang/core/ir/transform/multiplanar_external_texture.h
   lang/core/ir/transform/preserve_padding.cc
   lang/core/ir/transform/preserve_padding.h
+  lang/core/ir/transform/remove_terminator_args.cc
+  lang/core/ir/transform/remove_terminator_args.h
   lang/core/ir/transform/robustness.cc
   lang/core/ir/transform/robustness.h
   lang/core/ir/transform/shader_io.cc
@@ -124,6 +126,7 @@
   lang/core/ir/transform/helper_test.h
   lang/core/ir/transform/multiplanar_external_texture_test.cc
   lang/core/ir/transform/preserve_padding_test.cc
+  lang/core/ir/transform/remove_terminator_args_test.cc
   lang/core/ir/transform/robustness_test.cc
   lang/core/ir/transform/std140_test.cc
   lang/core/ir/transform/value_to_let_test.cc
@@ -204,6 +207,7 @@
   lang/core/ir/transform/direct_variable_access_fuzz.cc
   lang/core/ir/transform/multiplanar_external_texture_fuzz.cc
   lang/core/ir/transform/preserve_padding_fuzz.cc
+  lang/core/ir/transform/remove_terminator_args_fuzz.cc
   lang/core/ir/transform/robustness_fuzz.cc
   lang/core/ir/transform/std140_fuzz.cc
   lang/core/ir/transform/value_to_let_fuzz.cc
diff --git a/src/tint/lang/core/ir/transform/BUILD.gn b/src/tint/lang/core/ir/transform/BUILD.gn
index 16765ad..131161f 100644
--- a/src/tint/lang/core/ir/transform/BUILD.gn
+++ b/src/tint/lang/core/ir/transform/BUILD.gn
@@ -70,6 +70,8 @@
     "multiplanar_external_texture.h",
     "preserve_padding.cc",
     "preserve_padding.h",
+    "remove_terminator_args.cc",
+    "remove_terminator_args.h",
     "robustness.cc",
     "robustness.h",
     "shader_io.cc",
@@ -124,6 +126,7 @@
       "helper_test.h",
       "multiplanar_external_texture_test.cc",
       "preserve_padding_test.cc",
+      "remove_terminator_args_test.cc",
       "robustness_test.cc",
       "std140_test.cc",
       "value_to_let_test.cc",
@@ -195,6 +198,7 @@
     "direct_variable_access_fuzz.cc",
     "multiplanar_external_texture_fuzz.cc",
     "preserve_padding_fuzz.cc",
+    "remove_terminator_args_fuzz.cc",
     "robustness_fuzz.cc",
     "std140_fuzz.cc",
     "value_to_let_fuzz.cc",
diff --git a/src/tint/lang/core/ir/transform/remove_terminator_args.cc b/src/tint/lang/core/ir/transform/remove_terminator_args.cc
new file mode 100644
index 0000000..dfe865b
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/remove_terminator_args.cc
@@ -0,0 +1,177 @@
+// 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/core/ir/transform/remove_terminator_args.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/builder.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+namespace tint::core::ir::transform {
+
+namespace {
+
+/// PIMPL state for the transform.
+struct State {
+    /// The IR module.
+    Module& ir;
+
+    /// The IR builder.
+    Builder b{ir};
+
+    /// The type manager.
+    core::type::Manager& ty{ir.Types()};
+
+    /// A list of terminators that need to have their arguments cleared.
+    Vector<Terminator*, 8> terminators_to_clear{};
+
+    /// Process the module.
+    void Process() {
+        // Loop over every instruction looking for control instructions.
+        for (auto* inst : ir.Instructions()) {
+            tint::Switch(
+                inst,
+                [&](If* i) {  //
+                    RemoveExitArgs(i);
+                },
+                [&](Loop* l) {  //
+                    RemoveExitArgs(l);
+                    RemoveBlockParams(l->Body(), l->Initializer()->Front());
+                    RemoveBlockParams(l->Continuing(), l->Body()->Front());
+                },
+                [&](Switch* s) {  //
+                    RemoveExitArgs(s);
+                });
+
+            // Remove arguments from all terminators that we found.
+            for (auto* terminator : terminators_to_clear) {
+                if (auto* breakif = terminator->As<BreakIf>()) {
+                    // We retain the condition operand on break_if instructions.
+                    breakif->SetOperands(Vector{breakif->Condition()});
+                } else {
+                    terminator->ClearOperands();
+                }
+            }
+            terminators_to_clear.Clear();
+        }
+    }
+
+    /// Remove the arguments from all exit instructions inside a control instruction.
+    /// @param ci the control instruction
+    void RemoveExitArgs(ControlInstruction* ci) {
+        // Loop over all of the instruction results.
+        for (size_t i = 0; i < ci->Results().Length(); i++) {
+            auto* result = ci->Result(i);
+
+            // Create a variable to hold the result, and insert it before the control instruction.
+            auto* var = b.Var(ty.ptr<function>(result->Type()));
+            var->InsertBefore(ci);
+
+            // Store to the variable before each exit instruction.
+            for (auto exit : ci->Exits()) {
+                Value* value = nullptr;
+                if (auto* breakif = exit.Value()->As<BreakIf>()) {
+                    value = breakif->ExitValues()[i];
+                } else {
+                    value = exit.Value()->Args()[i];
+                }
+                if (value) {
+                    auto* store = b.Store(var, value);
+                    store->InsertBefore(exit.Value());
+                }
+            }
+
+            // Replace the original result with a load from the variable that we created above.
+            auto* load = b.LoadWithResult(result, var);
+            load->InsertAfter(ci);
+        }
+
+        // Remove the arguments from the exits and the results from the control instruction.
+        for (auto exit : ci->Exits()) {
+            terminators_to_clear.Push(exit);
+        }
+        ci->ClearResults();
+    }
+
+    /// Remove block parameters and arguments from all branches to a block.
+    /// @param block the block
+    /// @param var_insertion_point the insertion point for variables used to replace parameters
+    void RemoveBlockParams(MultiInBlock* block, Instruction* var_insertion_point) {
+        for (size_t i = 0; i < block->Params().Length(); i++) {
+            auto* param = block->Params()[i];
+
+            // Create a variable to hold the parameter value, and insert it in the parent block.
+            auto* var = b.Var(ty.ptr<function>(param->Type()));
+            var->InsertBefore(var_insertion_point);
+
+            // Store to the variable before each branch.
+            for (auto* branch : block->InboundSiblingBranches()) {
+                Value* value = nullptr;
+                if (auto* breakif = branch->As<BreakIf>()) {
+                    value = breakif->NextIterValues()[i];
+                } else {
+                    value = branch->Args()[i];
+                }
+                if (value) {
+                    auto* store = b.Store(var, value);
+                    store->InsertBefore(branch);
+                }
+            }
+
+            // Replace the original result with a load from the variable that we created above.
+            auto* load = b.Load(var);
+            load->InsertBefore(block->Front());
+            param->ReplaceAllUsesWith(load->Result(0));
+        }
+
+        // Remove the arguments from the branches and the parameters from the block.
+        for (auto exit : block->InboundSiblingBranches()) {
+            terminators_to_clear.Push(exit);
+        }
+        block->SetParams({});
+    }
+};
+
+}  // namespace
+
+Result<SuccessType> RemoveTerminatorArgs(Module& ir) {
+    auto result = ValidateAndDumpIfNeeded(ir, "RemoveTerminatorArgs transform");
+    if (result != Success) {
+        return result;
+    }
+
+    State{ir}.Process();
+
+    return Success;
+}
+
+}  // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/remove_terminator_args.h b/src/tint/lang/core/ir/transform/remove_terminator_args.h
new file mode 100644
index 0000000..98fbc65
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/remove_terminator_args.h
@@ -0,0 +1,49 @@
+// 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.
+
+#ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_REMOVE_TERMINATOR_ARGS_H_
+#define SRC_TINT_LANG_CORE_IR_TRANSFORM_REMOVE_TERMINATOR_ARGS_H_
+
+#include "src/tint/utils/result/result.h"
+
+// Forward declarations.
+namespace tint::core::ir {
+class Module;
+}
+
+namespace tint::core::ir::transform {
+
+/// RemoveTerminatorArgs is a transform that removes all arguments from terminator instructions and
+/// replaces them with stores to temporary variables instead. This is needed to prepare codegen for
+/// textual languages.
+/// @param module the module to transform
+/// @returns success or failure
+Result<SuccessType> RemoveTerminatorArgs(Module& module);
+
+}  // namespace tint::core::ir::transform
+
+#endif  // SRC_TINT_LANG_CORE_IR_TRANSFORM_REMOVE_TERMINATOR_ARGS_H_
diff --git a/src/tint/lang/core/ir/transform/remove_terminator_args_fuzz.cc b/src/tint/lang/core/ir/transform/remove_terminator_args_fuzz.cc
new file mode 100644
index 0000000..4cefe7f
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/remove_terminator_args_fuzz.cc
@@ -0,0 +1,50 @@
+// 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/core/ir/transform/remove_terminator_args.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void RemoveTerminatorArgsFuzzer(Module& module) {
+    if (auto res = RemoveTerminatorArgs(module); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of RemoveTerminatorArgs failed IR validation\n" << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::RemoveTerminatorArgsFuzzer);
diff --git a/src/tint/lang/core/ir/transform/remove_terminator_args_test.cc b/src/tint/lang/core/ir/transform/remove_terminator_args_test.cc
new file mode 100644
index 0000000..2fed34f
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/remove_terminator_args_test.cc
@@ -0,0 +1,911 @@
+// 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/core/ir/transform/remove_terminator_args.h"
+
+#include <utility>
+
+#include "src/tint/lang/core/ir/transform/helper_test.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+using namespace tint::core::fluent_types;     // NOLINT
+using namespace tint::core::number_suffixes;  // NOLINT
+
+using IR_RemoveTerminatorArgsTest = TransformTest;
+
+TEST_F(IR_RemoveTerminatorArgsTest, NoModify_If) {
+    auto* func = b.Function("foo", ty.void_());
+    b.Append(func->Block(), [&] {
+        auto* ifelse = b.If(true);
+        b.Append(ifelse->True(), [&] {  //
+            b.ExitIf(ifelse);
+        });
+        b.Append(ifelse->False(), [&] {  //
+            b.ExitIf(ifelse);
+        });
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = func():void {
+  $B1: {
+    if true [t: $B2, f: $B3] {  # if_1
+      $B2: {  # true
+        exit_if  # if_1
+      }
+      $B3: {  # false
+        exit_if  # if_1
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = src;
+
+    Run(RemoveTerminatorArgs);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveTerminatorArgsTest, NoModify_Switch) {
+    auto* func = b.Function("foo", ty.void_());
+    b.Append(func->Block(), [&] {
+        auto* swtch = b.Switch(42_i);
+
+        auto* case_a = b.Case(swtch, Vector{b.Constant(1_i)});
+        b.Append(case_a, [&] {  //
+            b.ExitSwitch(swtch);
+        });
+
+        auto* case_b = b.Case(swtch, Vector{b.Constant(2_i)});
+        b.Append(case_b, [&] {  //
+            b.ExitSwitch(swtch);
+        });
+
+        auto* def_case = b.DefaultCase(swtch);
+        b.Append(def_case, [&] {  //
+            b.ExitSwitch(swtch);
+        });
+
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = func():void {
+  $B1: {
+    switch 42i [c: (1i, $B2), c: (2i, $B3), c: (default, $B4)] {  # switch_1
+      $B2: {  # case
+        exit_switch  # switch_1
+      }
+      $B3: {  # case
+        exit_switch  # switch_1
+      }
+      $B4: {  # case
+        exit_switch  # switch_1
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = src;
+
+    Run(RemoveTerminatorArgs);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveTerminatorArgsTest, NoModify_Loop) {
+    auto* func = b.Function("foo", ty.void_());
+    b.Append(func->Block(), [&] {
+        auto* loop = b.Loop();
+
+        b.Append(loop->Initializer(), [&] {  //
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] {  //
+            auto* if_ = b.If(true);
+            b.Append(if_->True(), [&] {  //
+                b.ExitLoop(loop);
+            });
+            b.Continue(loop);
+        });
+        b.Append(loop->Continuing(), [&] {  //
+            b.BreakIf(loop, true);
+        });
+
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = func():void {
+  $B1: {
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        if true [t: $B5] {  # if_1
+          $B5: {  # true
+            exit_loop  # loop_1
+          }
+        }
+        continue  # -> $B4
+      }
+      $B4: {  # continuing
+        break_if true  # -> [t: exit_loop loop_1, f: $B3]
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = src;
+
+    Run(RemoveTerminatorArgs);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveTerminatorArgsTest, IfResults) {
+    auto* func = b.Function("foo", ty.void_());
+    b.Append(func->Block(), [&] {
+        auto* res_a = b.InstructionResult(ty.i32());
+        auto* res_b = b.InstructionResult(ty.u32());
+
+        auto* ifelse = b.If(true);
+        ifelse->SetResults(Vector{res_a, res_b});
+        b.Append(ifelse->True(), [&] {  //
+            b.ExitIf(ifelse, 1_i, 42_u);
+        });
+        b.Append(ifelse->False(), [&] {  //
+            b.ExitIf(ifelse, 42_i, 1_u);
+        });
+
+        // Use the results to make sure the uses get updated.
+        b.Add<i32>(res_a, 1_i);
+        b.Multiply<u32>(res_b, 2_u);
+
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = func():void {
+  $B1: {
+    %2:i32, %3:u32 = if true [t: $B2, f: $B3] {  # if_1
+      $B2: {  # true
+        exit_if 1i, 42u  # if_1
+      }
+      $B3: {  # false
+        exit_if 42i, 1u  # if_1
+      }
+    }
+    %4:i32 = add %2, 1i
+    %5:u32 = mul %3, 2u
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func():void {
+  $B1: {
+    %2:ptr<function, i32, read_write> = var
+    %3:ptr<function, u32, read_write> = var
+    if true [t: $B2, f: $B3] {  # if_1
+      $B2: {  # true
+        store %2, 1i
+        store %3, 42u
+        exit_if  # if_1
+      }
+      $B3: {  # false
+        store %2, 42i
+        store %3, 1u
+        exit_if  # if_1
+      }
+    }
+    %4:u32 = load %3
+    %5:i32 = load %2
+    %6:i32 = add %5, 1i
+    %7:u32 = mul %4, 2u
+    ret
+  }
+}
+)";
+
+    Run(RemoveTerminatorArgs);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveTerminatorArgsTest, SwitchResults) {
+    auto* func = b.Function("foo", ty.void_());
+    b.Append(func->Block(), [&] {
+        auto* res_a = b.InstructionResult(ty.i32());
+        auto* res_b = b.InstructionResult(ty.u32());
+
+        auto* swtch = b.Switch(42_i);
+        swtch->SetResults(Vector{res_a, res_b});
+
+        auto* case_a = b.Case(swtch, Vector{b.Constant(1_i)});
+        b.Append(case_a, [&] {  //
+            b.ExitSwitch(swtch, 1_i, 2_u);
+        });
+
+        auto* case_b = b.Case(swtch, Vector{b.Constant(2_i)});
+        b.Append(case_b, [&] {  //
+            b.ExitSwitch(swtch, 3_i, 4_u);
+        });
+
+        auto* def_case = b.DefaultCase(swtch);
+        b.Append(def_case, [&] {  //
+            b.ExitSwitch(swtch, 5_i, 6_u);
+        });
+
+        // Use the results to make sure the uses get updated.
+        b.Add<i32>(res_a, 1_i);
+        b.Multiply<u32>(res_b, 2_u);
+
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = func():void {
+  $B1: {
+    %2:i32, %3:u32 = switch 42i [c: (1i, $B2), c: (2i, $B3), c: (default, $B4)] {  # switch_1
+      $B2: {  # case
+        exit_switch 1i, 2u  # switch_1
+      }
+      $B3: {  # case
+        exit_switch 3i, 4u  # switch_1
+      }
+      $B4: {  # case
+        exit_switch 5i, 6u  # switch_1
+      }
+    }
+    %4:i32 = add %2, 1i
+    %5:u32 = mul %3, 2u
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func():void {
+  $B1: {
+    %2:ptr<function, i32, read_write> = var
+    %3:ptr<function, u32, read_write> = var
+    switch 42i [c: (1i, $B2), c: (2i, $B3), c: (default, $B4)] {  # switch_1
+      $B2: {  # case
+        store %2, 1i
+        store %3, 2u
+        exit_switch  # switch_1
+      }
+      $B3: {  # case
+        store %2, 3i
+        store %3, 4u
+        exit_switch  # switch_1
+      }
+      $B4: {  # case
+        store %2, 5i
+        store %3, 6u
+        exit_switch  # switch_1
+      }
+    }
+    %4:u32 = load %3
+    %5:i32 = load %2
+    %6:i32 = add %5, 1i
+    %7:u32 = mul %4, 2u
+    ret
+  }
+}
+)";
+
+    Run(RemoveTerminatorArgs);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveTerminatorArgsTest, Loop_Results) {
+    auto* func = b.Function("foo", ty.void_());
+    b.Append(func->Block(), [&] {
+        auto* res_a = b.InstructionResult(ty.i32());
+        auto* res_b = b.InstructionResult(ty.u32());
+
+        auto* loop = b.Loop();
+        loop->SetResults(Vector{res_a, res_b});
+
+        b.Append(loop->Initializer(), [&] {  //
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] {  //
+            auto* if_ = b.If(true);
+            b.Append(if_->True(), [&] {  //
+                b.ExitLoop(loop, 1_i, 2_u);
+            });
+            b.Continue(loop);
+        });
+        b.Append(loop->Continuing(), [&] {  //
+            b.BreakIf(loop, true, Empty, Vector{b.Constant(3_i), b.Constant(4_u)});
+        });
+
+        // Use the results to make sure the uses get updated.
+        b.Add<i32>(res_a, 1_i);
+        b.Multiply<u32>(res_b, 2_u);
+
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = func():void {
+  $B1: {
+    %2:i32, %3:u32 = loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        if true [t: $B5] {  # if_1
+          $B5: {  # true
+            exit_loop 1i, 2u  # loop_1
+          }
+        }
+        continue  # -> $B4
+      }
+      $B4: {  # continuing
+        break_if true exit_loop: [ 3i, 4u ]  # -> [t: exit_loop loop_1, f: $B3]
+      }
+    }
+    %4:i32 = add %2, 1i
+    %5:u32 = mul %3, 2u
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func():void {
+  $B1: {
+    %2:ptr<function, i32, read_write> = var
+    %3:ptr<function, u32, read_write> = var
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        if true [t: $B5] {  # if_1
+          $B5: {  # true
+            store %2, 1i
+            store %3, 2u
+            exit_loop  # loop_1
+          }
+        }
+        continue  # -> $B4
+      }
+      $B4: {  # continuing
+        store %2, 3i
+        store %3, 4u
+        break_if true  # -> [t: exit_loop loop_1, f: $B3]
+      }
+    }
+    %4:u32 = load %3
+    %5:i32 = load %2
+    %6:i32 = add %5, 1i
+    %7:u32 = mul %4, 2u
+    ret
+  }
+}
+)";
+
+    Run(RemoveTerminatorArgs);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveTerminatorArgsTest, Loop_BodyParams) {
+    auto* func = b.Function("foo", ty.void_());
+    b.Append(func->Block(), [&] {
+        auto* param_a = b.BlockParam(ty.i32());
+        auto* param_b = b.BlockParam(ty.u32());
+
+        auto* loop = b.Loop();
+        loop->Body()->SetParams(Vector{param_a, param_b});
+
+        b.Append(loop->Initializer(), [&] {  //
+            b.NextIteration(loop, 1_i, 2_u);
+        });
+        b.Append(loop->Body(), [&] {  //
+            // Use the parameters to make sure the uses get updated.
+            b.Add<i32>(param_a, 1_i);
+            b.Multiply<u32>(param_b, 2_u);
+
+            auto* if_ = b.If(true);
+            b.Append(if_->True(), [&] {  //
+                b.ExitLoop(loop);
+            });
+            b.Continue(loop);
+        });
+        b.Append(loop->Continuing(), [&] {  //
+            b.BreakIf(loop, true, Vector{b.Constant(3_i), b.Constant(4_u)}, Empty);
+        });
+
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = func():void {
+  $B1: {
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        next_iteration 1i, 2u  # -> $B3
+      }
+      $B3 (%2:i32, %3:u32): {  # body
+        %4:i32 = add %2, 1i
+        %5:u32 = mul %3, 2u
+        if true [t: $B5] {  # if_1
+          $B5: {  # true
+            exit_loop  # loop_1
+          }
+        }
+        continue  # -> $B4
+      }
+      $B4: {  # continuing
+        break_if true next_iteration: [ 3i, 4u ]  # -> [t: exit_loop loop_1, f: $B3]
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func():void {
+  $B1: {
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        %2:ptr<function, i32, read_write> = var
+        store %2, 1i
+        %3:ptr<function, u32, read_write> = var
+        store %3, 2u
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        %4:u32 = load %3
+        %5:i32 = load %2
+        %6:i32 = add %5, 1i
+        %7:u32 = mul %4, 2u
+        if true [t: $B5] {  # if_1
+          $B5: {  # true
+            exit_loop  # loop_1
+          }
+        }
+        continue  # -> $B4
+      }
+      $B4: {  # continuing
+        store %2, 3i
+        store %3, 4u
+        break_if true  # -> [t: exit_loop loop_1, f: $B3]
+      }
+    }
+    ret
+  }
+}
+)";
+
+    Run(RemoveTerminatorArgs);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveTerminatorArgsTest, Loop_ContinuingParams) {
+    auto* func = b.Function("foo", ty.void_());
+    b.Append(func->Block(), [&] {
+        auto* param_a = b.BlockParam(ty.i32());
+        auto* param_b = b.BlockParam(ty.u32());
+
+        auto* loop = b.Loop();
+        loop->Continuing()->SetParams(Vector{param_a, param_b});
+
+        b.Append(loop->Initializer(), [&] {  //
+            b.NextIteration(loop);
+        });
+        b.Append(loop->Body(), [&] {  //
+            auto* if_ = b.If(true);
+            b.Append(if_->True(), [&] {  //
+                b.ExitLoop(loop);
+            });
+            b.Continue(loop, 1_i, 2_u);
+        });
+        b.Append(loop->Continuing(), [&] {
+            // Use the parameters to make sure the uses get updated.
+            b.Add<i32>(param_a, 1_i);
+            b.Multiply<u32>(param_b, 2_u);
+
+            b.BreakIf(loop, true);
+        });
+
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = func():void {
+  $B1: {
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        if true [t: $B5] {  # if_1
+          $B5: {  # true
+            exit_loop  # loop_1
+          }
+        }
+        continue 1i, 2u  # -> $B4
+      }
+      $B4 (%2:i32, %3:u32): {  # continuing
+        %4:i32 = add %2, 1i
+        %5:u32 = mul %3, 2u
+        break_if true  # -> [t: exit_loop loop_1, f: $B3]
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func():void {
+  $B1: {
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        %2:ptr<function, i32, read_write> = var
+        %3:ptr<function, u32, read_write> = var
+        if true [t: $B5] {  # if_1
+          $B5: {  # true
+            exit_loop  # loop_1
+          }
+        }
+        store %2, 1i
+        store %3, 2u
+        continue  # -> $B4
+      }
+      $B4: {  # continuing
+        %4:u32 = load %3
+        %5:i32 = load %2
+        %6:i32 = add %5, 1i
+        %7:u32 = mul %4, 2u
+        break_if true  # -> [t: exit_loop loop_1, f: $B3]
+      }
+    }
+    ret
+  }
+}
+)";
+
+    Run(RemoveTerminatorArgs);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveTerminatorArgsTest, Loop_BreakIfWithTwoArgLists) {
+    auto* func = b.Function("foo", ty.void_());
+    b.Append(func->Block(), [&] {
+        auto* res_a = b.InstructionResult(ty.i32());
+        auto* res_b = b.InstructionResult(ty.u32());
+        auto* param_a = b.BlockParam(ty.f32());
+        auto* param_b = b.BlockParam(ty.i32());
+
+        auto* loop = b.Loop();
+        loop->SetResults(Vector{res_a, res_b});
+        loop->Body()->SetParams(Vector{param_a, param_b});
+
+        b.Append(loop->Initializer(), [&] {  //
+            b.NextIteration(loop, 1_f, 2_i);
+        });
+        b.Append(loop->Body(), [&] {
+            // Use the parameters to make sure the uses get updated.
+            b.Subtract<f32>(param_a, 1_f);
+            b.Divide<i32>(param_b, 2_i);
+
+            auto* if_ = b.If(true);
+            b.Append(if_->True(), [&] {  //
+                b.ExitLoop(loop, 3_i, 4_u);
+            });
+            b.Continue(loop);
+        });
+        b.Append(loop->Continuing(), [&] {  //
+            b.BreakIf(loop, true, Vector{b.Constant(5_f), b.Constant(6_i)},
+                      Vector{b.Constant(7_i), b.Constant(8_u)});
+        });
+
+        // Use the results to make sure the uses get updated.
+        b.Add<i32>(res_a, 1_i);
+        b.Multiply<u32>(res_b, 2_u);
+
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = func():void {
+  $B1: {
+    %2:i32, %3:u32 = loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        next_iteration 1.0f, 2i  # -> $B3
+      }
+      $B3 (%4:f32, %5:i32): {  # body
+        %6:f32 = sub %4, 1.0f
+        %7:i32 = div %5, 2i
+        if true [t: $B5] {  # if_1
+          $B5: {  # true
+            exit_loop 3i, 4u  # loop_1
+          }
+        }
+        continue  # -> $B4
+      }
+      $B4: {  # continuing
+        break_if true next_iteration: [ 5.0f, 6i ] exit_loop: [ 7i, 8u ]  # -> [t: exit_loop loop_1, f: $B3]
+      }
+    }
+    %8:i32 = add %2, 1i
+    %9:u32 = mul %3, 2u
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func():void {
+  $B1: {
+    %2:ptr<function, i32, read_write> = var
+    %3:ptr<function, u32, read_write> = var
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        %4:ptr<function, f32, read_write> = var
+        store %4, 1.0f
+        %5:ptr<function, i32, read_write> = var
+        store %5, 2i
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        %6:i32 = load %5
+        %7:f32 = load %4
+        %8:f32 = sub %7, 1.0f
+        %9:i32 = div %6, 2i
+        if true [t: $B5] {  # if_1
+          $B5: {  # true
+            store %2, 3i
+            store %3, 4u
+            exit_loop  # loop_1
+          }
+        }
+        continue  # -> $B4
+      }
+      $B4: {  # continuing
+        store %2, 7i
+        store %3, 8u
+        store %4, 5.0f
+        store %5, 6i
+        break_if true  # -> [t: exit_loop loop_1, f: $B3]
+      }
+    }
+    %10:u32 = load %3
+    %11:i32 = load %2
+    %12:i32 = add %11, 1i
+    %13:u32 = mul %10, 2u
+    ret
+  }
+}
+)";
+
+    Run(RemoveTerminatorArgs);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveTerminatorArgsTest, UndefResults) {
+    auto* func = b.Function("foo", ty.void_());
+    b.Append(func->Block(), [&] {
+        auto* res_a = b.InstructionResult(ty.i32());
+        auto* res_b = b.InstructionResult(ty.u32());
+
+        auto* ifelse = b.If(true);
+        ifelse->SetResults(Vector{res_a, res_b});
+        b.Append(ifelse->True(), [&] {  //
+            b.ExitIf(ifelse, 1_i, nullptr);
+        });
+        b.Append(ifelse->False(), [&] {  //
+            b.ExitIf(ifelse, nullptr, 2_u);
+        });
+
+        // Use the results to make sure the uses get updated.
+        b.Add<i32>(res_a, 1_i);
+        b.Multiply<u32>(res_b, 2_u);
+
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = func():void {
+  $B1: {
+    %2:i32, %3:u32 = if true [t: $B2, f: $B3] {  # if_1
+      $B2: {  # true
+        exit_if 1i, undef  # if_1
+      }
+      $B3: {  # false
+        exit_if undef, 2u  # if_1
+      }
+    }
+    %4:i32 = add %2, 1i
+    %5:u32 = mul %3, 2u
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func():void {
+  $B1: {
+    %2:ptr<function, i32, read_write> = var
+    %3:ptr<function, u32, read_write> = var
+    if true [t: $B2, f: $B3] {  # if_1
+      $B2: {  # true
+        store %2, 1i
+        exit_if  # if_1
+      }
+      $B3: {  # false
+        store %3, 2u
+        exit_if  # if_1
+      }
+    }
+    %4:u32 = load %3
+    %5:i32 = load %2
+    %6:i32 = add %5, 1i
+    %7:u32 = mul %4, 2u
+    ret
+  }
+}
+)";
+
+    Run(RemoveTerminatorArgs);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(IR_RemoveTerminatorArgsTest, UndefBlockParams) {
+    auto* func = b.Function("foo", ty.void_());
+    b.Append(func->Block(), [&] {
+        auto* param_a = b.BlockParam(ty.i32());
+        auto* param_b = b.BlockParam(ty.u32());
+
+        auto* loop = b.Loop();
+        loop->Body()->SetParams(Vector{param_a, param_b});
+
+        b.Append(loop->Initializer(), [&] {  //
+            b.NextIteration(loop, 1_i, nullptr);
+        });
+        b.Append(loop->Body(), [&] {  //
+            // Use the parameters to make sure the uses get updated.
+            b.Add<i32>(param_a, 1_i);
+            b.Multiply<u32>(param_b, 2_u);
+
+            auto* if_ = b.If(true);
+            b.Append(if_->True(), [&] {  //
+                b.ExitLoop(loop);
+            });
+            b.Continue(loop);
+        });
+        b.Append(loop->Continuing(), [&] {  //
+            b.BreakIf(loop, true, Vector{b.Constant(3_i), nullptr}, Empty);
+        });
+
+        b.Return(func);
+    });
+
+    auto* src = R"(
+%foo = func():void {
+  $B1: {
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        next_iteration 1i, undef  # -> $B3
+      }
+      $B3 (%2:i32, %3:u32): {  # body
+        %4:i32 = add %2, 1i
+        %5:u32 = mul %3, 2u
+        if true [t: $B5] {  # if_1
+          $B5: {  # true
+            exit_loop  # loop_1
+          }
+        }
+        continue  # -> $B4
+      }
+      $B4: {  # continuing
+        break_if true next_iteration: [ 3i, undef ]  # -> [t: exit_loop loop_1, f: $B3]
+      }
+    }
+    ret
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func():void {
+  $B1: {
+    loop [i: $B2, b: $B3, c: $B4] {  # loop_1
+      $B2: {  # initializer
+        %2:ptr<function, i32, read_write> = var
+        store %2, 1i
+        %3:ptr<function, u32, read_write> = var
+        next_iteration  # -> $B3
+      }
+      $B3: {  # body
+        %4:u32 = load %3
+        %5:i32 = load %2
+        %6:i32 = add %5, 1i
+        %7:u32 = mul %4, 2u
+        if true [t: $B5] {  # if_1
+          $B5: {  # true
+            exit_loop  # loop_1
+          }
+        }
+        continue  # -> $B4
+      }
+      $B4: {  # continuing
+        store %2, 3i
+        break_if true  # -> [t: exit_loop loop_1, f: $B3]
+      }
+    }
+    ret
+  }
+}
+)";
+
+    Run(RemoveTerminatorArgs);
+
+    EXPECT_EQ(expect, str());
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index f24b8eb..34906d4 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -1495,8 +1495,7 @@
         if (auto* to = s->To()) {
             auto* mv = to->Type()->As<core::type::MemoryView>();
             if (!mv) {
-                AddError(s, Store::kFromOperandOffset)
-                    << "store target operand is not a memory view";
+                AddError(s, Store::kToOperandOffset) << "store target operand is not a memory view";
                 return;
             }
             auto* value_type = from->Type();
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index 870d345..da74d0c 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -4510,9 +4510,9 @@
     auto res = ir::Validate(mod);
     ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.Str(),
-              R"(:4:15 error: store: store target operand is not a memory view
+              R"(:4:11 error: store: store target operand is not a memory view
     store %l, 42u
-              ^^^
+          ^^
 
 :2:3 note: in block
   $B1: {
diff --git a/src/tint/lang/glsl/writer/writer_ast_fuzz.cc b/src/tint/lang/glsl/writer/writer_ast_fuzz.cc
index 839e466..e555a75 100644
--- a/src/tint/lang/glsl/writer/writer_ast_fuzz.cc
+++ b/src/tint/lang/glsl/writer/writer_ast_fuzz.cc
@@ -43,7 +43,11 @@
     // Excessive values can cause OOM / timeouts in the PadStructs transform.
     static constexpr uint32_t kMaxOffset = 0x1000;
 
-    if (options.first_instance_offset > kMaxOffset) {
+    if (options.first_instance_offset && options.first_instance_offset > kMaxOffset) {
+        return false;
+    }
+
+    if (options.first_vertex_offset && options.first_vertex_offset > kMaxOffset) {
         return false;
     }
 
diff --git a/src/tint/lang/msl/writer/raise/raise.cc b/src/tint/lang/msl/writer/raise/raise.cc
index ad8cbfe..d3b5007 100644
--- a/src/tint/lang/msl/writer/raise/raise.cc
+++ b/src/tint/lang/msl/writer/raise/raise.cc
@@ -37,6 +37,7 @@
 #include "src/tint/lang/core/ir/transform/demote_to_helper.h"
 #include "src/tint/lang/core/ir/transform/multiplanar_external_texture.h"
 #include "src/tint/lang/core/ir/transform/preserve_padding.h"
+#include "src/tint/lang/core/ir/transform/remove_terminator_args.h"
 #include "src/tint/lang/core/ir/transform/robustness.h"
 #include "src/tint/lang/core/ir/transform/value_to_let.h"
 #include "src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.h"
@@ -111,6 +112,7 @@
 
     RUN_TRANSFORM(raise::ShaderIO, raise::ShaderIOConfig{options.emit_vertex_point_size});
     RUN_TRANSFORM(raise::ModuleScopeVars);
+    RUN_TRANSFORM(core::ir::transform::RemoveTerminatorArgs);
     RUN_TRANSFORM(core::ir::transform::ValueToLet);
     RUN_TRANSFORM(raise::BuiltinPolyfill);
 
diff --git a/src/tint/lang/spirv/writer/raise/merge_return.cc b/src/tint/lang/spirv/writer/raise/merge_return.cc
index 350afa4..077660e 100644
--- a/src/tint/lang/spirv/writer/raise/merge_return.cc
+++ b/src/tint/lang/spirv/writer/raise/merge_return.cc
@@ -129,6 +129,7 @@
     /// @param block the block to process
     void ProcessBlock(core::ir::Block* block) {
         core::ir::If* inner_if = nullptr;
+        Vector<core::ir::If*, 4> inner_if_stack;
         for (auto* inst = *block->begin(); inst;) {  // For each instruction in 'block'
             // As we're modifying the block that we're iterating over, grab the pointer to the next
             // instruction before (potentially) moving 'inst' to another block.
@@ -167,6 +168,7 @@
                     if (next && (next != fn_return || fn_return->Value()) &&
                         !tint::IsAnyOf<core::ir::Exit, core::ir::Unreachable>(next)) {
                         inner_if = CreateIfContinueExecution(ctrl);
+                        inner_if_stack.Push(inner_if);
                     }
                 }
             }
@@ -195,9 +197,10 @@
                 inner_if->True()->Append(b.ExitIf(inner_if));
             }
 
-            // Loop over the 'if' instructions, starting with the inner-most, and add any missing
-            // terminating instructions to the blocks holding the 'if'.
-            for (auto* i = inner_if; i; i = tint::As<core::ir::If>(i->Block()->Parent())) {
+            // Walk back down the stack of 'if' instructions that were created, and add any missing
+            // terminating instructions to the blocks holding them.
+            while (!inner_if_stack.IsEmpty()) {
+                auto* i = inner_if_stack.Pop();
                 if (!i->Block()->Terminator() && i->Block()->Parent()) {
                     // Append the exit instruction to the block holding the 'if'.
                     Vector<core::ir::InstructionResult*, 8> exit_args = i->Results();
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 9cb2f0a..7b7c0f1 100644
--- a/src/tint/lang/spirv/writer/raise/merge_return_test.cc
+++ b/src/tint/lang/spirv/writer/raise/merge_return_test.cc
@@ -1413,6 +1413,306 @@
     EXPECT_EQ(expect, str());
 }
 
+TEST_F(SpirvWriter_MergeReturnTest, IfElse_NestedConsecutives) {
+    auto* value = b.FunctionParam(ty.i32());
+    auto* func = b.Function("foo", ty.i32());
+    func->SetParams({value});
+
+    b.Append(func->Block(), [&] {
+        auto* outer = b.If(b.Equal(ty.bool_(), value, 1_i));
+        b.Append(outer->True(), [&] {
+            auto* middle_first = b.If(b.Equal(ty.bool_(), value, 2_i));
+            b.Append(middle_first->True(), [&] {  //
+                b.Return(func, 202_i);
+            });
+
+            auto* middle_second = b.If(b.Equal(ty.bool_(), value, 3_i));
+            b.Append(middle_second->True(), [&] {
+                auto* inner_first = b.If(b.Equal(ty.bool_(), value, 4_i));
+                b.Append(inner_first->True(), [&] {  //
+                    b.Return(func, 404_i);
+                });
+
+                auto* inner_second = b.If(b.Equal(ty.bool_(), value, 5_i));
+                b.Append(inner_second->True(), [&] {  //
+                    b.Return(func, 505_i);
+                });
+
+                b.ExitIf(middle_second);
+            });
+
+            b.ExitIf(outer);
+        });
+
+        b.Return(func, 606_i);
+    });
+
+    auto* src = R"(
+%foo = func(%2:i32):i32 {
+  $B1: {
+    %3:bool = eq %2, 1i
+    if %3 [t: $B2] {  # if_1
+      $B2: {  # true
+        %4:bool = eq %2, 2i
+        if %4 [t: $B3] {  # if_2
+          $B3: {  # true
+            ret 202i
+          }
+        }
+        %5:bool = eq %2, 3i
+        if %5 [t: $B4] {  # if_3
+          $B4: {  # true
+            %6:bool = eq %2, 4i
+            if %6 [t: $B5] {  # if_4
+              $B5: {  # true
+                ret 404i
+              }
+            }
+            %7:bool = eq %2, 5i
+            if %7 [t: $B6] {  # if_5
+              $B6: {  # true
+                ret 505i
+              }
+            }
+            exit_if  # if_3
+          }
+        }
+        exit_if  # if_1
+      }
+    }
+    ret 606i
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%2:i32):i32 {
+  $B1: {
+    %return_value:ptr<function, i32, read_write> = var
+    %continue_execution:ptr<function, bool, read_write> = var, true
+    %5:bool = eq %2, 1i
+    if %5 [t: $B2] {  # if_1
+      $B2: {  # true
+        %6:bool = eq %2, 2i
+        if %6 [t: $B3] {  # if_2
+          $B3: {  # true
+            store %continue_execution, false
+            store %return_value, 202i
+            exit_if  # if_2
+          }
+        }
+        %7:bool = load %continue_execution
+        if %7 [t: $B4] {  # if_3
+          $B4: {  # true
+            %8:bool = eq %2, 3i
+            if %8 [t: $B5] {  # if_4
+              $B5: {  # true
+                %9:bool = eq %2, 4i
+                if %9 [t: $B6] {  # if_5
+                  $B6: {  # true
+                    store %continue_execution, false
+                    store %return_value, 404i
+                    exit_if  # if_5
+                  }
+                }
+                %10:bool = load %continue_execution
+                if %10 [t: $B7] {  # if_6
+                  $B7: {  # true
+                    %11:bool = eq %2, 5i
+                    if %11 [t: $B8] {  # if_7
+                      $B8: {  # true
+                        store %continue_execution, false
+                        store %return_value, 505i
+                        exit_if  # if_7
+                      }
+                    }
+                    exit_if  # if_6
+                  }
+                }
+                exit_if  # if_4
+              }
+            }
+            exit_if  # if_3
+          }
+        }
+        exit_if  # if_1
+      }
+    }
+    %12:bool = load %continue_execution
+    if %12 [t: $B9] {  # if_8
+      $B9: {  # true
+        store %return_value, 606i
+        exit_if  # if_8
+      }
+    }
+    %13:i32 = load %return_value
+    ret %13
+  }
+}
+)";
+
+    Run(MergeReturn);
+
+    EXPECT_EQ(expect, str());
+}
+
+TEST_F(SpirvWriter_MergeReturnTest, IfElse_NestedConsecutives_WithResults) {
+    auto* value = b.FunctionParam(ty.i32());
+    auto* func = b.Function("foo", ty.i32());
+    func->SetParams({value});
+
+    b.Append(func->Block(), [&] {
+        auto* outer_result = b.InstructionResult(ty.i32());
+        auto* outer = b.If(b.Equal(ty.bool_(), value, 1_i));
+        outer->SetResults(Vector{outer_result});
+        b.Append(outer->True(), [&] {
+            auto* middle_first = b.If(b.Equal(ty.bool_(), value, 2_i));
+            b.Append(middle_first->True(), [&] {  //
+                b.Return(func, 202_i);
+            });
+
+            auto middle_result = b.InstructionResult(ty.i32());
+            auto* middle_second = b.If(b.Equal(ty.bool_(), value, 3_i));
+            middle_second->SetResults(Vector{middle_result});
+            b.Append(middle_second->True(), [&] {
+                auto* inner_first = b.If(b.Equal(ty.bool_(), value, 4_i));
+                b.Append(inner_first->True(), [&] {  //
+                    b.Return(func, 404_i);
+                });
+
+                auto inner_result = b.InstructionResult(ty.i32());
+                auto* inner_second = b.If(b.Equal(ty.bool_(), value, 5_i));
+                inner_second->SetResults(Vector{inner_result});
+                b.Append(inner_second->True(), [&] {  //
+                    b.ExitIf(inner_second, 505_i);
+                });
+
+                b.ExitIf(middle_second, inner_result);
+            });
+
+            b.ExitIf(outer, middle_result);
+        });
+
+        b.Return(func, outer_result);
+    });
+
+    auto* src = R"(
+%foo = func(%2:i32):i32 {
+  $B1: {
+    %3:bool = eq %2, 1i
+    %4:i32 = if %3 [t: $B2] {  # if_1
+      $B2: {  # true
+        %5:bool = eq %2, 2i
+        if %5 [t: $B3] {  # if_2
+          $B3: {  # true
+            ret 202i
+          }
+        }
+        %6:bool = eq %2, 3i
+        %7:i32 = if %6 [t: $B4] {  # if_3
+          $B4: {  # true
+            %8:bool = eq %2, 4i
+            if %8 [t: $B5] {  # if_4
+              $B5: {  # true
+                ret 404i
+              }
+            }
+            %9:bool = eq %2, 5i
+            %10:i32 = if %9 [t: $B6] {  # if_5
+              $B6: {  # true
+                exit_if 505i  # if_5
+              }
+              # implicit false block: exit_if undef
+            }
+            exit_if %10  # if_3
+          }
+          # implicit false block: exit_if undef
+        }
+        exit_if %7  # if_1
+      }
+      # implicit false block: exit_if undef
+    }
+    ret %4
+  }
+}
+)";
+    EXPECT_EQ(src, str());
+
+    auto* expect = R"(
+%foo = func(%2:i32):i32 {
+  $B1: {
+    %return_value:ptr<function, i32, read_write> = var
+    %continue_execution:ptr<function, bool, read_write> = var, true
+    %5:bool = eq %2, 1i
+    %6:i32 = if %5 [t: $B2] {  # if_1
+      $B2: {  # true
+        %7:bool = eq %2, 2i
+        if %7 [t: $B3] {  # if_2
+          $B3: {  # true
+            store %continue_execution, false
+            store %return_value, 202i
+            exit_if  # if_2
+          }
+        }
+        %8:bool = load %continue_execution
+        %9:i32 = if %8 [t: $B4] {  # if_3
+          $B4: {  # true
+            %10:bool = eq %2, 3i
+            %11:i32 = if %10 [t: $B5] {  # if_4
+              $B5: {  # true
+                %12:bool = eq %2, 4i
+                if %12 [t: $B6] {  # if_5
+                  $B6: {  # true
+                    store %continue_execution, false
+                    store %return_value, 404i
+                    exit_if  # if_5
+                  }
+                }
+                %13:bool = load %continue_execution
+                %14:i32 = if %13 [t: $B7] {  # if_6
+                  $B7: {  # true
+                    %15:bool = eq %2, 5i
+                    %16:i32 = if %15 [t: $B8] {  # if_7
+                      $B8: {  # true
+                        exit_if 505i  # if_7
+                      }
+                      # implicit false block: exit_if undef
+                    }
+                    exit_if %16  # if_6
+                  }
+                  # implicit false block: exit_if undef
+                }
+                exit_if %14  # if_4
+              }
+              # implicit false block: exit_if undef
+            }
+            exit_if %11  # if_3
+          }
+          # implicit false block: exit_if undef
+        }
+        exit_if %9  # if_1
+      }
+      # implicit false block: exit_if undef
+    }
+    %17:bool = load %continue_execution
+    if %17 [t: $B9] {  # if_8
+      $B9: {  # true
+        store %return_value, %6
+        exit_if  # if_8
+      }
+    }
+    %18:i32 = load %return_value
+    ret %18
+  }
+}
+)";
+
+    Run(MergeReturn);
+
+    EXPECT_EQ(expect, str());
+}
+
 TEST_F(SpirvWriter_MergeReturnTest, Loop_UnconditionalReturnInBody) {
     auto* func = b.Function("foo", ty.i32());
 
diff --git a/src/tint/lang/wgsl/ast/BUILD.bazel b/src/tint/lang/wgsl/ast/BUILD.bazel
index be215b2..e038bf6 100644
--- a/src/tint/lang/wgsl/ast/BUILD.bazel
+++ b/src/tint/lang/wgsl/ast/BUILD.bazel
@@ -205,7 +205,6 @@
   deps = [
     "//src/tint/api/common",
     "//src/tint/lang/core",
-    "//src/tint/lang/core/constant",
     "//src/tint/lang/core/type",
     "//src/tint/lang/wgsl",
     "//src/tint/lang/wgsl/features",
diff --git a/src/tint/lang/wgsl/ast/BUILD.cmake b/src/tint/lang/wgsl/ast/BUILD.cmake
index 3d36157..6afc090 100644
--- a/src/tint/lang/wgsl/ast/BUILD.cmake
+++ b/src/tint/lang/wgsl/ast/BUILD.cmake
@@ -206,7 +206,6 @@
 tint_target_add_dependencies(tint_lang_wgsl_ast lib
   tint_api_common
   tint_lang_core
-  tint_lang_core_constant
   tint_lang_core_type
   tint_lang_wgsl
   tint_lang_wgsl_features
diff --git a/src/tint/lang/wgsl/ast/BUILD.gn b/src/tint/lang/wgsl/ast/BUILD.gn
index 165f2a7..a1b43d7 100644
--- a/src/tint/lang/wgsl/ast/BUILD.gn
+++ b/src/tint/lang/wgsl/ast/BUILD.gn
@@ -208,7 +208,6 @@
   deps = [
     "${tint_src_dir}/api/common",
     "${tint_src_dir}/lang/core",
-    "${tint_src_dir}/lang/core/constant",
     "${tint_src_dir}/lang/core/type",
     "${tint_src_dir}/lang/wgsl",
     "${tint_src_dir}/lang/wgsl/features",
diff --git a/src/tint/lang/wgsl/ast/builder.h b/src/tint/lang/wgsl/ast/builder.h
index 03cc5b0..33fbe7a 100644
--- a/src/tint/lang/wgsl/ast/builder.h
+++ b/src/tint/lang/wgsl/ast/builder.h
@@ -28,34 +28,17 @@
 #ifndef SRC_TINT_LANG_WGSL_AST_BUILDER_H_
 #define SRC_TINT_LANG_WGSL_AST_BUILDER_H_
 
-#include <string>
-#include <unordered_set>
 #include <utility>
 
 #include "src/tint/api/common/override_id.h"
 
-#include "src/tint/lang/core/constant/manager.h"
 #include "src/tint/lang/core/fluent_types.h"
 #include "src/tint/lang/core/interpolation_sampling.h"
 #include "src/tint/lang/core/interpolation_type.h"
 #include "src/tint/lang/core/number.h"
-#include "src/tint/lang/core/type/array.h"
-#include "src/tint/lang/core/type/bool.h"
-#include "src/tint/lang/core/type/depth_texture.h"
-#include "src/tint/lang/core/type/external_texture.h"
-#include "src/tint/lang/core/type/f16.h"
-#include "src/tint/lang/core/type/f32.h"
-#include "src/tint/lang/core/type/i32.h"
-#include "src/tint/lang/core/type/matrix.h"
-#include "src/tint/lang/core/type/multisampled_texture.h"
-#include "src/tint/lang/core/type/pointer.h"
-#include "src/tint/lang/core/type/sampled_texture.h"
+#include "src/tint/lang/core/texel_format.h"
 #include "src/tint/lang/core/type/sampler_kind.h"
-#include "src/tint/lang/core/type/storage_texture.h"
 #include "src/tint/lang/core/type/texture_dimension.h"
-#include "src/tint/lang/core/type/u32.h"
-#include "src/tint/lang/core/type/vector.h"
-#include "src/tint/lang/core/type/void.h"
 #include "src/tint/lang/wgsl/ast/alias.h"
 #include "src/tint/lang/wgsl/ast/assignment_statement.h"
 #include "src/tint/lang/wgsl/ast/binary_expression.h"
@@ -117,6 +100,8 @@
 #include "src/tint/lang/wgsl/builtin_fn.h"
 #include "src/tint/lang/wgsl/extension.h"
 #include "src/tint/utils/id/generation_id.h"
+#include "src/tint/utils/memory/block_allocator.h"
+#include "src/tint/utils/symbol/symbol_table.h"
 #include "src/tint/utils/text/string.h"
 
 #ifdef CURRENTLY_IN_TINT_PUBLIC_HEADER
diff --git a/src/tint/lang/wgsl/diagnostic_rule.h b/src/tint/lang/wgsl/diagnostic_rule.h
index cf2d8f0..f84158f 100644
--- a/src/tint/lang/wgsl/diagnostic_rule.h
+++ b/src/tint/lang/wgsl/diagnostic_rule.h
@@ -38,7 +38,6 @@
 #define SRC_TINT_LANG_WGSL_DIAGNOSTIC_RULE_H_
 
 #include <cstdint>
-#include <string>
 #include <variant>
 
 #include "src/tint/utils/traits/traits.h"
diff --git a/src/tint/lang/wgsl/diagnostic_rule.h.tmpl b/src/tint/lang/wgsl/diagnostic_rule.h.tmpl
index 9948d1c..328f533 100644
--- a/src/tint/lang/wgsl/diagnostic_rule.h.tmpl
+++ b/src/tint/lang/wgsl/diagnostic_rule.h.tmpl
@@ -15,7 +15,6 @@
 #define SRC_TINT_LANG_WGSL_DIAGNOSTIC_RULE_H_
 
 #include <cstdint>
-#include <string>
 #include <variant>
 
 #include "src/tint/utils/traits/traits.h"
diff --git a/src/tint/lang/wgsl/ls/document.cc b/src/tint/lang/wgsl/ls/document.cc
index 2a60da4..8cc416b 100644
--- a/src/tint/lang/wgsl/ls/document.cc
+++ b/src/tint/lang/wgsl/ls/document.cc
@@ -51,8 +51,10 @@
 
 langsvr::Result<langsvr::SuccessType> Server::Handle(
     const lsp::TextDocumentDidOpenNotification& n) {
+    wgsl::reader::Options options;
+    options.allowed_features = wgsl::AllowedFeatures::Everything();
     auto source = std::make_unique<Source::File>(n.text_document.uri, n.text_document.text);
-    auto program = wgsl::reader::Parse(source.get());
+    auto program = wgsl::reader::Parse(source.get(), options);
     auto file =
         std::make_shared<File>(std::move(source), n.text_document.version, std::move(program));
     files_.Add(n.text_document.uri, file);
@@ -82,8 +84,10 @@
             utf8 = utf8.substr(0, utf8_start) + edit->text + utf8.substr(utf8_end);
         }
     }
+    wgsl::reader::Options options;
+    options.allowed_features = wgsl::AllowedFeatures::Everything();
     auto source = std::make_unique<Source::File>(n.text_document.uri, utf8);
-    auto program = wgsl::reader::Parse(source.get());
+    auto program = wgsl::reader::Parse(source.get(), options);
     *file = std::make_shared<File>(std::move(source), n.text_document.version, std::move(program));
     return PublishDiagnostics(**file);
 }