Import Tint changes from Dawn

Changes:
  - 36589ed1e5b097b3bff70d9fc78d87d095e673dd [tint][utils] Make it more likely for fuzzers to decode c... by Ben Clayton <bclayton@google.com>
  - 71926f1b61d3d450f02d4bb38004efea2bafd22a [tint][sem] Remove unused member variable by Ben Clayton <bclayton@google.com>
  - e050d30803181a8a0c0e34f38715a1b416486dd9 [tint][fuzz][ir] Skip programs that use the framebuffer f... by Ben Clayton <bclayton@google.com>
  - 48de84dfe6458204fe463d1249e336680302ff71 Revert "[tint][fuzz] Enable AllowedFeatures::Everything()" by Ben Clayton <bclayton@google.com>
  - e9aa8c372d01ec8c6312d31193593277cd75afb6 [tint][fuzz] Enable AllowedFeatures::Everything() by Ben Clayton <bclayton@google.com>
  - bc1d5ca8313cbeb054e88386e97f3d72b750512c [tint][fuzz][ast] Add ArrayLengthFromUniform fuzzer by Ben Clayton <bclayton@google.com>
  - fe59714b752b36a012fb328d8de0d2aa68cf6bf2 [tint][fuzz][ir] Add ValueToLet fuzzer by Ben Clayton <bclayton@google.com>
  - 12680c44defe03a180bd3b15260a2c618ef61d02 [tint][fuzz][ir] Add ZeroInitWorkgroupMemory fuzzer by Ben Clayton <bclayton@google.com>
  - 3a4e074edbac10d04e60a2f720756c7fb7e5259d [tint][fuzz][ir] Add VectorizeScalarMatrixConstructors fu... by Ben Clayton <bclayton@google.com>
  - 28afc120e634c1c71ea6f5ad3bc448930e2aa9c6 [tint][fuzz][ir] Add Std140 fuzzer by Ben Clayton <bclayton@google.com>
  - 4241ea260d27c7d578fe0d1657b6be4f76026374 [tint][fuzz][ir] Add Robustness fuzzer by Ben Clayton <bclayton@google.com>
  - 13798bc31d4de2b3da27e76cdd253c219b3149fb [tint][fuzz][ir] Add PreservePadding fuzzer by Ben Clayton <bclayton@google.com>
  - c132ccda7e8ac61fc5a42c7b1bdd3d0fc4a2a44e [tint][fuzz][ir] Add MultiplanarExternalTexture fuzzer by Ben Clayton <bclayton@google.com>
  - a1025ffd8f9bb45b7f8597881de348ea26d58012 [tint][fuzz][ir] Add DirectVariableAccess fuzzer by Ben Clayton <bclayton@google.com>
  - 4ae06486516687352952a4ea1adacdff565926ae [tint][fuzz][ir] Add DemoteToHelper fuzzer by Ben Clayton <bclayton@google.com>
  - 7fa78c5be0a84d5128ada355b2d40c34ca5c81f0 [tint][fuzz][ir] Add ConversionPolyfill fuzzer by Ben Clayton <bclayton@google.com>
  - 2189ab286cef0c8405ccb20bab03615c47b5ed4d [tint][fuzz][ir] Add CombineAccessInstructions fuzzer by Ben Clayton <bclayton@google.com>
  - cb9ccf87002b881cd6fbcddd9db1937220cfd9bd [tint][fuzz][ir] Add BuiltinPolyfill fuzzer by Ben Clayton <bclayton@google.com>
  - f792ce6e091b89be72c660e67bccbdcd9f319d8b [tint][fuzz][ir] Add BlockDecoratedStructs fuzzer by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: 36589ed1e5b097b3bff70d9fc78d87d095e673dd
Change-Id: I73c0316bdb08690f72f1c96c8e8f33713ee5c82d
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/185560
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ben Clayton <bclayton@google.com>
Kokoro: Kokoro <noreply+kokoro@google.com>
diff --git a/src/tint/cmd/fuzz/ir/fuzz.cc b/src/tint/cmd/fuzz/ir/fuzz.cc
index 8fbf4cb..3ea4363 100644
--- a/src/tint/cmd/fuzz/ir/fuzz.cc
+++ b/src/tint/cmd/fuzz/ir/fuzz.cc
@@ -46,6 +46,7 @@
 bool IsUnsupported(const ast::Enable* enable) {
     for (auto ext : enable->extensions) {
         switch (ext->name) {
+            case tint::wgsl::Extension::kChromiumExperimentalFramebufferFetch:
             case tint::wgsl::Extension::kChromiumExperimentalPixelLocal:
             case tint::wgsl::Extension::kChromiumExperimentalPushConstant:
             case tint::wgsl::Extension::kChromiumInternalDualSourceBlending:
diff --git a/src/tint/lang/core/ir/transform/BUILD.cmake b/src/tint/lang/core/ir/transform/BUILD.cmake
index 223dbee..1ae8efb 100644
--- a/src/tint/lang/core/ir/transform/BUILD.cmake
+++ b/src/tint/lang/core/ir/transform/BUILD.cmake
@@ -189,23 +189,42 @@
   lang/core/ir/transform/bgra8unorm_polyfill_fuzz.cc
   lang/core/ir/transform/binary_polyfill_fuzz.cc
   lang/core/ir/transform/binding_remapper_fuzz.cc
+  lang/core/ir/transform/block_decorated_structs_fuzz.cc
+  lang/core/ir/transform/builtin_polyfill_fuzz.cc
+  lang/core/ir/transform/combine_access_instructions_fuzz.cc
+  lang/core/ir/transform/conversion_polyfill_fuzz.cc
+  lang/core/ir/transform/demote_to_helper_fuzz.cc
+  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/robustness_fuzz.cc
+  lang/core/ir/transform/std140_fuzz.cc
+  lang/core/ir/transform/value_to_let_fuzz.cc
+  lang/core/ir/transform/vectorize_scalar_matrix_constructors_fuzz.cc
+  lang/core/ir/transform/zero_init_workgroup_memory_fuzz.cc
 )
 
 tint_target_add_dependencies(tint_lang_core_ir_transform_fuzz fuzz
   tint_api_common
+  tint_api_options
   tint_cmd_fuzz_ir_fuzz
+  tint_lang_core
+  tint_lang_core_constant
   tint_lang_core_ir
   tint_lang_core_ir_transform
+  tint_lang_core_type
   tint_utils_bytes
   tint_utils_containers
   tint_utils_diagnostic
   tint_utils_ice
+  tint_utils_id
   tint_utils_macros
   tint_utils_math
   tint_utils_memory
   tint_utils_reflection
   tint_utils_result
   tint_utils_rtti
+  tint_utils_symbol
   tint_utils_text
   tint_utils_traits
 )
diff --git a/src/tint/lang/core/ir/transform/BUILD.gn b/src/tint/lang/core/ir/transform/BUILD.gn
index bd81416..95de9fc 100644
--- a/src/tint/lang/core/ir/transform/BUILD.gn
+++ b/src/tint/lang/core/ir/transform/BUILD.gn
@@ -182,22 +182,41 @@
     "bgra8unorm_polyfill_fuzz.cc",
     "binary_polyfill_fuzz.cc",
     "binding_remapper_fuzz.cc",
+    "block_decorated_structs_fuzz.cc",
+    "builtin_polyfill_fuzz.cc",
+    "combine_access_instructions_fuzz.cc",
+    "conversion_polyfill_fuzz.cc",
+    "demote_to_helper_fuzz.cc",
+    "direct_variable_access_fuzz.cc",
+    "multiplanar_external_texture_fuzz.cc",
+    "preserve_padding_fuzz.cc",
+    "robustness_fuzz.cc",
+    "std140_fuzz.cc",
+    "value_to_let_fuzz.cc",
+    "vectorize_scalar_matrix_constructors_fuzz.cc",
+    "zero_init_workgroup_memory_fuzz.cc",
   ]
   deps = [
     "${tint_src_dir}/api/common",
+    "${tint_src_dir}/api/options",
     "${tint_src_dir}/cmd/fuzz/ir:fuzz",
+    "${tint_src_dir}/lang/core",
+    "${tint_src_dir}/lang/core/constant",
     "${tint_src_dir}/lang/core/ir",
     "${tint_src_dir}/lang/core/ir/transform",
+    "${tint_src_dir}/lang/core/type",
     "${tint_src_dir}/utils/bytes",
     "${tint_src_dir}/utils/containers",
     "${tint_src_dir}/utils/diagnostic",
     "${tint_src_dir}/utils/ice",
+    "${tint_src_dir}/utils/id",
     "${tint_src_dir}/utils/macros",
     "${tint_src_dir}/utils/math",
     "${tint_src_dir}/utils/memory",
     "${tint_src_dir}/utils/reflection",
     "${tint_src_dir}/utils/result",
     "${tint_src_dir}/utils/rtti",
+    "${tint_src_dir}/utils/symbol",
     "${tint_src_dir}/utils/text",
     "${tint_src_dir}/utils/traits",
   ]
diff --git a/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc b/src/tint/lang/core/ir/transform/block_decorated_structs_fuzz.cc
new file mode 100644
index 0000000..a13427d
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/block_decorated_structs_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/block_decorated_structs.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void BlockDecoratedStructsFuzzer(Module& module) {
+    if (auto res = BlockDecoratedStructs(module); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of BlockDecoratedStructs failed IR validation\n" << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::BlockDecoratedStructsFuzzer);
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.h b/src/tint/lang/core/ir/transform/builtin_polyfill.h
index 13ac4fe..9795a9c 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.h
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.h
@@ -30,6 +30,7 @@
 
 #include <string>
 
+#include "src/tint/utils/reflection/reflection.h"
 #include "src/tint/utils/result/result.h"
 
 // Forward declarations.
@@ -77,6 +78,21 @@
     /// Should `pack4xU8Clamp()` be polyfilled?
     /// TODO(tint:1497): remove the option once the bug in DXC is fixed.
     bool pack_4xu8_clamp = false;
+
+    /// Reflection for this class
+    TINT_REFLECT(BuiltinPolyfillConfig,
+                 clamp_int,
+                 count_leading_zeros,
+                 count_trailing_zeros,
+                 extract_bits,
+                 first_leading_bit,
+                 first_trailing_bit,
+                 insert_bits,
+                 saturate,
+                 texture_sample_base_clamp_to_edge_2d_f32,
+                 dot_4x8_packed,
+                 pack_unpack_4x8,
+                 pack_4xu8_clamp);
 };
 
 /// BuiltinPolyfill is a transform that replaces calls to builtin functions and uses of other core
@@ -88,4 +104,11 @@
 
 }  // namespace tint::core::ir::transform
 
+namespace tint {
+
+/// Reflection for BuiltinPolyfillLevel
+TINT_REFLECT_ENUM_RANGE(tint::core::ir::transform::BuiltinPolyfillLevel, kNone, kFull);
+
+}  // namespace tint
+
 #endif  // SRC_TINT_LANG_CORE_IR_TRANSFORM_BUILTIN_POLYFILL_H_
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill_fuzz.cc b/src/tint/lang/core/ir/transform/builtin_polyfill_fuzz.cc
new file mode 100644
index 0000000..298bbaa
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill_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/builtin_polyfill.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void BuiltinPolyfillFuzzer(Module& module, BuiltinPolyfillConfig config) {
+    if (auto res = BuiltinPolyfill(module, config); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of BuiltinPolyfill failed IR validation\n" << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::BuiltinPolyfillFuzzer);
diff --git a/src/tint/lang/core/ir/transform/combine_access_instructions_fuzz.cc b/src/tint/lang/core/ir/transform/combine_access_instructions_fuzz.cc
new file mode 100644
index 0000000..ea0f01d
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/combine_access_instructions_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/combine_access_instructions.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void CombineAccessInstructionsFuzzer(Module& module) {
+    if (auto res = CombineAccessInstructions(module); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of CombineAccessInstructions failed IR validation\n" << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::CombineAccessInstructionsFuzzer);
diff --git a/src/tint/lang/core/ir/transform/conversion_polyfill.h b/src/tint/lang/core/ir/transform/conversion_polyfill.h
index 91836d1..ff17872 100644
--- a/src/tint/lang/core/ir/transform/conversion_polyfill.h
+++ b/src/tint/lang/core/ir/transform/conversion_polyfill.h
@@ -30,6 +30,7 @@
 
 #include <string>
 
+#include "src/tint/utils/reflection/reflection.h"
 #include "src/tint/utils/result/result.h"
 
 // Forward declarations.
@@ -43,6 +44,9 @@
 struct ConversionPolyfillConfig {
     /// Should converting floating point values to integers be polyfilled?
     bool ftoi = false;
+
+    /// Reflection for this class
+    TINT_REFLECT(ConversionPolyfillConfig, ftoi);
 };
 
 /// ConversionPolyfill is a transform that modifies convert instructions to prepare them for raising
diff --git a/src/tint/lang/core/ir/transform/conversion_polyfill_fuzz.cc b/src/tint/lang/core/ir/transform/conversion_polyfill_fuzz.cc
new file mode 100644
index 0000000..9d8f963
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/conversion_polyfill_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/conversion_polyfill.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void ConversionPolyfillFuzzer(Module& module, ConversionPolyfillConfig config) {
+    if (auto res = ConversionPolyfill(module, config); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of ConversionPolyfill failed IR validation\n" << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::ConversionPolyfillFuzzer);
diff --git a/src/tint/lang/core/ir/transform/demote_to_helper_fuzz.cc b/src/tint/lang/core/ir/transform/demote_to_helper_fuzz.cc
new file mode 100644
index 0000000..ad896ab
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/demote_to_helper_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/demote_to_helper.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void DemoteToHelperFuzzer(Module& module) {
+    if (auto res = DemoteToHelper(module); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of DemoteToHelper failed IR validation\n" << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::DemoteToHelperFuzzer);
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access.h b/src/tint/lang/core/ir/transform/direct_variable_access.h
index ffa169b..ced1943 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access.h
+++ b/src/tint/lang/core/ir/transform/direct_variable_access.h
@@ -28,6 +28,7 @@
 #ifndef SRC_TINT_LANG_CORE_IR_TRANSFORM_DIRECT_VARIABLE_ACCESS_H_
 #define SRC_TINT_LANG_CORE_IR_TRANSFORM_DIRECT_VARIABLE_ACCESS_H_
 
+#include "src/tint/utils/reflection/reflection.h"
 #include "src/tint/utils/result/result.h"
 
 // Forward declarations.
@@ -43,6 +44,9 @@
     bool transform_private = false;
     /// If true, then 'function' sub-object pointer arguments will be transformed.
     bool transform_function = false;
+
+    /// Reflection for this class
+    TINT_REFLECT(DirectVariableAccessOptions, transform_private, transform_function);
 };
 
 /// DirectVariableAccess is a transform that transforms pointer parameters in the 'storage',
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access_fuzz.cc b/src/tint/lang/core/ir/transform/direct_variable_access_fuzz.cc
new file mode 100644
index 0000000..33f0306
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/direct_variable_access_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/direct_variable_access.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void DirectVariableAccessFuzzer(Module& module, DirectVariableAccessOptions options) {
+    if (auto res = DirectVariableAccess(module, options); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of DirectVariableAccess failed IR validation\n" << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::DirectVariableAccessFuzzer);
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 1d3e5ad..d2d3263 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
@@ -34,6 +34,7 @@
 #include "src/tint/lang/core/ir/validator.h"
 #include "src/tint/lang/core/type/external_texture.h"
 #include "src/tint/lang/core/type/sampled_texture.h"
+#include "src/tint/utils/result/result.h"
 
 using namespace tint::core::fluent_types;     // NOLINT
 using namespace tint::core::number_suffixes;  // NOLINT
@@ -75,7 +76,7 @@
     Function* gamma_correction = nullptr;
 
     /// Process the module.
-    void Process() {
+    Result<SuccessType> Process() {
         // Find module-scope variables that need to be replaced.
         if (!ir.root_block->IsEmpty()) {
             Vector<Instruction*, 4> to_remove;
@@ -86,7 +87,9 @@
                 }
                 auto* ptr = var->Result(0)->Type()->As<core::type::Pointer>();
                 if (ptr->StoreType()->Is<core::type::ExternalTexture>()) {
-                    ReplaceVar(var);
+                    if (auto res = ReplaceVar(var); TINT_UNLIKELY(res != Success)) {
+                        return res.Failure();
+                    }
                     to_remove.Push(var);
                 }
             }
@@ -105,6 +108,8 @@
                 }
             }
         }
+
+        return Success;
     }
 
     /// @returns a 2D sampled texture type with a f32 sampled type
@@ -114,11 +119,15 @@
 
     /// Replace an external texture variable declaration.
     /// @param old_var the variable declaration to replace
-    void ReplaceVar(Var* old_var) {
+    Result<SuccessType> ReplaceVar(Var* old_var) {
         auto name = ir.NameOf(old_var);
         auto bp = old_var->BindingPoint();
         auto itr = options.bindings_map.find(bp.value());
-        TINT_ASSERT_OR_RETURN(itr != options.bindings_map.end());
+        if (TINT_UNLIKELY(itr == options.bindings_map.end())) {
+            std::stringstream err;
+            err << "ExternalTextureOptions missing binding entry for " << bp.value();
+            return Failure{err.str()};
+        }
         const auto& new_binding_points = itr->second;
 
         // Create a sampled texture for the first plane.
@@ -150,6 +159,8 @@
         // Replace all uses of the old variable with the new ones.
         ReplaceUses(old_var->Result(0), plane_0->Result(0), plane_1->Result(0),
                     external_texture_params->Result(0));
+
+        return Success;
     }
 
     /// Replace an external texture function parameter.
@@ -586,9 +597,7 @@
         return result;
     }
 
-    State{options, ir}.Process();
-
-    return Success;
+    return State{options, ir}.Process();
 }
 
 }  // namespace tint::core::ir::transform
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture_fuzz.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture_fuzz.cc
new file mode 100644
index 0000000..4a017cf
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture_fuzz.cc
@@ -0,0 +1,51 @@
+// 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/multiplanar_external_texture.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void MultiplanarExternalTextureFuzzer(Module& module, const ExternalTextureOptions& options) {
+    if (auto res = MultiplanarExternalTexture(module, options); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of MultiplanarExternalTexture failed IR validation\n"
+                   << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::MultiplanarExternalTextureFuzzer);
diff --git a/src/tint/lang/core/ir/transform/preserve_padding_fuzz.cc b/src/tint/lang/core/ir/transform/preserve_padding_fuzz.cc
new file mode 100644
index 0000000..1dddfd9
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/preserve_padding_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/preserve_padding.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void PreservePaddingFuzzer(Module& module) {
+    if (auto res = PreservePadding(module); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of PreservePadding failed IR validation\n" << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::PreservePaddingFuzzer);
diff --git a/src/tint/lang/core/ir/transform/robustness.cc b/src/tint/lang/core/ir/transform/robustness.cc
index 43cd948..228b822 100644
--- a/src/tint/lang/core/ir/transform/robustness.cc
+++ b/src/tint/lang/core/ir/transform/robustness.cc
@@ -141,6 +141,7 @@
 
         // TODO(jrprice): Handle config.bindings_ignored.
         if (!config.bindings_ignored.empty()) {
+            // Also update robustness_fuzz.cc
             TINT_UNIMPLEMENTED();
         }
     }
diff --git a/src/tint/lang/core/ir/transform/robustness.h b/src/tint/lang/core/ir/transform/robustness.h
index 6a4d7bd..a69fac8 100644
--- a/src/tint/lang/core/ir/transform/robustness.h
+++ b/src/tint/lang/core/ir/transform/robustness.h
@@ -32,6 +32,7 @@
 #include <unordered_set>
 
 #include "src/tint/api/common/binding_point.h"
+#include "src/tint/utils/reflection/reflection.h"
 #include "src/tint/utils/result/result.h"
 
 // Forward declarations.
@@ -67,6 +68,19 @@
 
     /// Should the transform skip index clamping on runtime-sized arrays?
     bool disable_runtime_sized_array_index_clamping = false;
+
+    /// Reflection for this class
+    TINT_REFLECT(RobustnessConfig,
+                 clamp_value,
+                 clamp_texture,
+                 clamp_function,
+                 clamp_private,
+                 clamp_push_constant,
+                 clamp_storage,
+                 clamp_uniform,
+                 clamp_workgroup,
+                 bindings_ignored,
+                 disable_runtime_sized_array_index_clamping);
 };
 
 /// Robustness is a transform that prevents out-of-bounds memory accesses.
diff --git a/src/tint/lang/core/ir/transform/robustness_fuzz.cc b/src/tint/lang/core/ir/transform/robustness_fuzz.cc
new file mode 100644
index 0000000..930ed84
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/robustness_fuzz.cc
@@ -0,0 +1,55 @@
+// 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/robustness.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void RobustnessFuzzer(Module& module, RobustnessConfig config) {
+    if (!config.bindings_ignored.empty()) {
+        // TODO(jrprice): Handle config.bindings_ignored.
+        return;
+    }
+
+    if (auto res = Robustness(module, config); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of Robustness failed IR validation\n" << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::RobustnessFuzzer);
diff --git a/src/tint/lang/core/ir/transform/std140_fuzz.cc b/src/tint/lang/core/ir/transform/std140_fuzz.cc
new file mode 100644
index 0000000..86e5730
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/std140_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/std140.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void Std140Fuzzer(Module& module) {
+    if (auto res = Std140(module); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of Std140 failed IR validation\n" << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::Std140Fuzzer);
diff --git a/src/tint/lang/core/ir/transform/value_to_let_fuzz.cc b/src/tint/lang/core/ir/transform/value_to_let_fuzz.cc
new file mode 100644
index 0000000..84564e2
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/value_to_let_fuzz.cc
@@ -0,0 +1,65 @@
+// 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/value_to_let.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+bool CanRun(Module& module) {
+    for (auto* inst : module.Instructions()) {
+        if (inst->Results().Length() > 1) {
+            // ValueToLet assumes that all multi-result instructions have been replaced
+            return false;
+        }
+    }
+    return true;
+}
+
+void ValueToLetFuzzer(Module& module) {
+    if (!CanRun(module)) {
+        return;
+    }
+
+    if (auto res = ValueToLet(module); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of ValueToLet failed IR validation\n" << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::ValueToLetFuzzer);
diff --git a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors_fuzz.cc b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors_fuzz.cc
new file mode 100644
index 0000000..c3fb9c1
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors_fuzz.cc
@@ -0,0 +1,51 @@
+// 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/vectorize_scalar_matrix_constructors.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void VectorizeScalarMatrixConstructorsFuzzer(Module& module) {
+    if (auto res = VectorizeScalarMatrixConstructors(module); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of VectorizeScalarMatrixConstructors failed IR validation\n"
+                   << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::VectorizeScalarMatrixConstructorsFuzzer);
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_fuzz.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_fuzz.cc
new file mode 100644
index 0000000..3c7f868
--- /dev/null
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_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/zero_init_workgroup_memory.h"
+
+#include "src/tint/cmd/fuzz/ir/fuzz.h"
+#include "src/tint/lang/core/ir/validator.h"
+
+namespace tint::core::ir::transform {
+namespace {
+
+void ZeroInitWorkgroupMemoryFuzzer(Module& module) {
+    if (auto res = ZeroInitWorkgroupMemory(module); res != Success) {
+        return;
+    }
+
+    Capabilities capabilities;
+    if (auto res = Validate(module, capabilities); res != Success) {
+        TINT_ICE() << "result of ZeroInitWorkgroupMemory failed IR validation\n" << res.Failure();
+    }
+}
+
+}  // namespace
+}  // namespace tint::core::ir::transform
+
+TINT_IR_MODULE_FUZZER(tint::core::ir::transform::ZeroInitWorkgroupMemoryFuzzer);
diff --git a/src/tint/lang/wgsl/ast/transform/BUILD.cmake b/src/tint/lang/wgsl/ast/transform/BUILD.cmake
index 05dae10..13dc91b 100644
--- a/src/tint/lang/wgsl/ast/transform/BUILD.cmake
+++ b/src/tint/lang/wgsl/ast/transform/BUILD.cmake
@@ -250,10 +250,12 @@
 tint_add_target(tint_lang_wgsl_ast_transform_fuzz fuzz
   lang/wgsl/ast/transform/add_block_attribute_fuzz.cc
   lang/wgsl/ast/transform/add_empty_entry_point_fuzz.cc
+  lang/wgsl/ast/transform/array_length_from_uniform_fuzz.cc
   lang/wgsl/ast/transform/zero_init_workgroup_memory_fuzz.cc
 )
 
 tint_target_add_dependencies(tint_lang_wgsl_ast_transform_fuzz fuzz
+  tint_api_common
   tint_lang_core
   tint_lang_core_constant
   tint_lang_core_type
diff --git a/src/tint/lang/wgsl/ast/transform/BUILD.gn b/src/tint/lang/wgsl/ast/transform/BUILD.gn
index 7b2d224..63bcb03 100644
--- a/src/tint/lang/wgsl/ast/transform/BUILD.gn
+++ b/src/tint/lang/wgsl/ast/transform/BUILD.gn
@@ -240,9 +240,11 @@
     sources = [
       "add_block_attribute_fuzz.cc",
       "add_empty_entry_point_fuzz.cc",
+      "array_length_from_uniform_fuzz.cc",
       "zero_init_workgroup_memory_fuzz.cc",
     ]
     deps = [
+      "${tint_src_dir}/api/common",
       "${tint_src_dir}/lang/core",
       "${tint_src_dir}/lang/core/constant",
       "${tint_src_dir}/lang/core/type",
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 1cb8c08..636f8f3 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
@@ -90,7 +90,7 @@
     /// @param program the source program
     /// @param in the input transform data
     /// @param out the output transform data
-    explicit State(const Program& program, const DataMap& in, DataMap& out)
+    State(const Program& program, const DataMap& in, DataMap& out)
         : src(program), outputs(out), cfg(in.Get<Config>()) {}
 
     /// Runs the transform
@@ -333,6 +333,7 @@
     return State{src, inputs, outputs}.Run();
 }
 
+ArrayLengthFromUniform::Config::Config() = default;
 ArrayLengthFromUniform::Config::Config(BindingPoint ubo_bp) : ubo_binding(ubo_bp) {}
 ArrayLengthFromUniform::Config::Config(const Config&) = default;
 ArrayLengthFromUniform::Config& ArrayLengthFromUniform::Config::operator=(const Config&) = default;
diff --git a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.h b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.h
index 12d05fb..ed3191e 100644
--- a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.h
+++ b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform.h
@@ -33,6 +33,7 @@
 
 #include "src/tint/api/common/binding_point.h"
 #include "src/tint/lang/wgsl/ast/transform/transform.h"
+#include "src/tint/utils/reflection/reflection.h"
 
 namespace tint::ast::transform {
 
@@ -70,6 +71,9 @@
     /// Configuration options for the ArrayLengthFromUniform transform.
     struct Config final : public Castable<Config, Data> {
         /// Constructor
+        Config();
+
+        /// Constructor
         /// @param ubo_bp the binding point to use for the generated uniform buffer.
         explicit Config(BindingPoint ubo_bp);
 
@@ -88,6 +92,9 @@
 
         /// The mapping from binding point to the index for the buffer size lookup.
         std::unordered_map<BindingPoint, uint32_t> bindpoint_to_size_index;
+
+        /// Reflection for this class
+        TINT_REFLECT(Config, ubo_binding, bindpoint_to_size_index);
     };
 
     /// Information produced about what the transform did.
diff --git a/src/tint/lang/wgsl/ast/transform/array_length_from_uniform_fuzz.cc b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform_fuzz.cc
new file mode 100644
index 0000000..525f57e
--- /dev/null
+++ b/src/tint/lang/wgsl/ast/transform/array_length_from_uniform_fuzz.cc
@@ -0,0 +1,83 @@
+// 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/cmd/fuzz/wgsl/fuzz.h"
+#include "src/tint/lang/core/type/pointer.h"
+#include "src/tint/lang/wgsl/ast/let.h"
+#include "src/tint/lang/wgsl/ast/module.h"
+#include "src/tint/lang/wgsl/ast/transform/array_length_from_uniform.h"
+#include "src/tint/lang/wgsl/sem/variable.h"
+
+namespace tint::ast::transform {
+namespace {
+
+bool CanRun(const Program& program, const ArrayLengthFromUniform::Config& config) {
+    // The ArrayLengthFromUniform depends on SimplifyPointers, and cannot handle pointers to arrays
+    // via lets (directly or via struct pointers). Reject all shaders with pointer lets.
+    for (auto* node : program.ASTNodes().Objects()) {
+        if (auto* let = node->As<ast::Let>()) {
+            if (auto* sem = program.Sem().Get(let)) {
+                if (auto* ptr = sem->Type()->As<core::type::Pointer>()) {
+                    return false;
+                }
+            }
+        }
+    }
+
+    for (auto& global : program.AST().GlobalVariables()) {
+        if (auto* sem = program.Sem().Get<sem::GlobalVariable>(global)) {
+            if (sem->Attributes().binding_point == config.ubo_binding) {
+                // Might cause binding point collision
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+void ArrayLengthFromUniformFuzzer(const Program& program,
+                                  const ArrayLengthFromUniform::Config& config) {
+    if (!CanRun(program, config)) {
+        return;
+    }
+
+    DataMap inputs;
+    inputs.Add<ArrayLengthFromUniform::Config>(std::move(config));
+
+    DataMap outputs;
+    if (auto result = ArrayLengthFromUniform{}.Apply(program, inputs, outputs)) {
+        if (!result->IsValid()) {
+            TINT_ICE() << "ArrayLengthFromUniform returned invalid program:\n"
+                       << result->Diagnostics();
+        }
+    }
+}
+
+}  // namespace
+}  // namespace tint::ast::transform
+
+TINT_WGSL_PROGRAM_FUZZER(tint::ast::transform::ArrayLengthFromUniformFuzzer);
diff --git a/src/tint/lang/wgsl/sem/variable.h b/src/tint/lang/wgsl/sem/variable.h
index 15ca981..bf97929 100644
--- a/src/tint/lang/wgsl/sem/variable.h
+++ b/src/tint/lang/wgsl/sem/variable.h
@@ -197,7 +197,6 @@
     const GlobalVariableAttributes& Attributes() const { return attributes_; }
 
   private:
-    std::optional<tint::BindingPoint> binding_point_;
     tint::OverrideId override_id_;
     UniqueVector<const GlobalVariable*, 4> transitively_referenced_overrides_;
     GlobalVariableAttributes attributes_;
diff --git a/src/tint/utils/bytes/decoder.h b/src/tint/utils/bytes/decoder.h
index a62d2e5..9f29bbf 100644
--- a/src/tint/utils/bytes/decoder.h
+++ b/src/tint/utils/bytes/decoder.h
@@ -34,6 +34,7 @@
 #include <string>
 #include <tuple>
 #include <unordered_map>
+#include <unordered_set>
 #include <utility>
 #include <vector>
 
@@ -132,7 +133,7 @@
     static Result<std::unordered_map<K, V>> Decode(Reader& reader) {
         std::unordered_map<K, V> out;
 
-        while (true) {
+        while (!reader.IsEOF()) {
             auto stop = bytes::Decode<bool>(reader);
             if (stop != Success) {
                 return stop.Failure();
@@ -155,6 +156,34 @@
     }
 };
 
+/// Decoder specialization for std::unordered_set
+template <typename V>
+struct Decoder<std::unordered_set<V>, void> {
+    /// Decode decodes the set from @p reader.
+    /// @param reader the reader to decode from
+    /// @returns the decoded set, or an error if the stream is too short.
+    static Result<std::unordered_set<V>> Decode(Reader& reader) {
+        std::unordered_set<V> out;
+
+        while (!reader.IsEOF()) {
+            auto stop = bytes::Decode<bool>(reader);
+            if (stop != Success) {
+                return stop.Failure();
+            }
+            if (stop.Get()) {
+                break;
+            }
+            auto val = bytes::Decode<V>(reader);
+            if (val != Success) {
+                return val.Failure();
+            }
+            out.emplace(std::move(val.Get()));
+        }
+
+        return out;
+    }
+};
+
 /// Decoder specialization for std::vector
 template <typename V>
 struct Decoder<std::vector<V>, void> {
@@ -164,7 +193,7 @@
     static Result<std::vector<V>> Decode(Reader& reader) {
         std::vector<V> out;
 
-        while (true) {
+        while (!reader.IsEOF()) {
             auto stop = bytes::Decode<bool>(reader);
             if (stop != Success) {
                 return stop.Failure();
diff --git a/src/tint/utils/bytes/decoder_test.cc b/src/tint/utils/bytes/decoder_test.cc
index d33dc42..125d7e6 100644
--- a/src/tint/utils/bytes/decoder_test.cc
+++ b/src/tint/utils/bytes/decoder_test.cc
@@ -30,9 +30,11 @@
 #include <string>
 #include <tuple>
 #include <unordered_map>
+#include <unordered_set>
 #include <utility>
 
 #include "gmock/gmock.h"
+#include "src/tint/utils/result/result.h"
 
 namespace tint {
 namespace {
@@ -144,14 +146,46 @@
                      0x00, 0x70, 0x08, 0x80,  //
                      0x01);
     auto reader = BufferReader{Slice{data}};
-    auto got = Decode<M>(reader);
-    EXPECT_THAT(got.Get(), testing::ContainerEq(M{
-                               std::pair<uint8_t, uint32_t>(0x10u, 0x2002u),
-                               std::pair<uint8_t, uint32_t>(0x30u, 0x4004u),
-                               std::pair<uint8_t, uint32_t>(0x50u, 0x6006u),
-                               std::pair<uint8_t, uint32_t>(0x70u, 0x8008u),
-                           }));
-    EXPECT_NE(Decode<M>(reader), Success);
+    {
+        auto got = Decode<M>(reader);
+        ASSERT_EQ(got, Success);
+        EXPECT_THAT(got.Get(), testing::ContainerEq(M{
+                                   std::pair<uint8_t, uint16_t>(0x10u, 0x2002u),
+                                   std::pair<uint8_t, uint16_t>(0x30u, 0x4004u),
+                                   std::pair<uint8_t, uint16_t>(0x50u, 0x6006u),
+                                   std::pair<uint8_t, uint16_t>(0x70u, 0x8008u),
+                               }));
+    }
+    {
+        auto got = Decode<M>(reader);
+        ASSERT_EQ(got, Success);
+        EXPECT_THAT(got.Get(), testing::IsEmpty());
+    }
+}
+
+TEST(BytesDecoderTest, UnorderedSet) {
+    using S = std::unordered_set<uint16_t>;
+    auto data = Data(0x00, 0x02, 0x20,  //
+                     0x00, 0x04, 0x40,  //
+                     0x00, 0x06, 0x60,  //
+                     0x00, 0x08, 0x80,  //
+                     0x01);
+    auto reader = BufferReader{Slice{data}};
+    {
+        auto got = Decode<S>(reader);
+        ASSERT_EQ(got, Success);
+        EXPECT_THAT(got.Get(), testing::ContainerEq(S{
+                                   0x2002u,
+                                   0x4004u,
+                                   0x6006u,
+                                   0x8008u,
+                               }));
+    }
+    {
+        auto got = Decode<S>(reader);
+        ASSERT_EQ(got, Success);
+        EXPECT_THAT(got.Get(), testing::IsEmpty());
+    }
 }
 
 TEST(BytesDecoderTest, Vector) {
@@ -162,14 +196,21 @@
                      0x00, 0x70,  //
                      0x01);
     auto reader = BufferReader{Slice{data}};
-    auto got = Decode<M>(reader);
-    EXPECT_THAT(got.Get(), testing::ContainerEq(M{
-                               0x10u,
-                               0x30u,
-                               0x50u,
-                               0x70u,
-                           }));
-    EXPECT_NE(Decode<M>(reader), Success);
+    {
+        auto got = Decode<M>(reader);
+        ASSERT_EQ(got, Success);
+        EXPECT_THAT(got.Get(), testing::ContainerEq(M{
+                                   0x10u,
+                                   0x30u,
+                                   0x50u,
+                                   0x70u,
+                               }));
+    }
+    {
+        auto got = Decode<M>(reader);
+        ASSERT_EQ(got, Success);
+        EXPECT_THAT(got.Get(), testing::IsEmpty());
+    }
 }
 
 TEST(BytesDecoderTest, Optional) {
diff --git a/src/tint/utils/bytes/reader.cc b/src/tint/utils/bytes/reader.cc
index 9c28d1d..fc2dfc2 100644
--- a/src/tint/utils/bytes/reader.cc
+++ b/src/tint/utils/bytes/reader.cc
@@ -41,4 +41,8 @@
     return n;
 }
 
+bool BufferReader::IsEOF() const {
+    return bytes_remaining_ == 0;
+}
+
 }  // namespace tint::bytes
diff --git a/src/tint/utils/bytes/reader.h b/src/tint/utils/bytes/reader.h
index 59e1864..3b5f51a 100644
--- a/src/tint/utils/bytes/reader.h
+++ b/src/tint/utils/bytes/reader.h
@@ -54,6 +54,9 @@
     /// then the end of the stream has been reached.
     virtual size_t Read(std::byte* out, size_t count) = 0;
 
+    /// @returns true if the Reader has no more bytes to read.
+    virtual bool IsEOF() const = 0;
+
     /// Reads an integer from the stream, performing byte swapping if the stream's endianness
     /// differs from the native endianness.
     /// If there are too few bytes remaining in the stream, then a failure is returned.
@@ -140,6 +143,9 @@
     /// @copydoc Reader::Read
     size_t Read(std::byte* out, size_t count) override;
 
+    /// @copydoc Reader::IsEOF
+    bool IsEOF() const override;
+
   private:
     /// The data to read from
     const std::byte* data_ = nullptr;
diff --git a/src/tint/utils/bytes/reader_test.cc b/src/tint/utils/bytes/reader_test.cc
index 04ad6af..2e90e88 100644
--- a/src/tint/utils/bytes/reader_test.cc
+++ b/src/tint/utils/bytes/reader_test.cc
@@ -39,47 +39,87 @@
 
 TEST(BufferReaderTest, IntegerBigEndian) {
     auto data = Data(0x10, 0x20, 0x30, 0x40);
-    auto u32 = BufferReader{Slice{data}}.Int<uint32_t>(Endianness::kBig);
+
+    BufferReader u32_reader{Slice{data}};
+    EXPECT_FALSE(u32_reader.IsEOF());
+    auto u32 = u32_reader.Int<uint32_t>(Endianness::kBig);
     EXPECT_EQ(u32, 0x10203040u);
-    auto i32 = BufferReader{Slice{data}}.Int<int32_t>(Endianness::kBig);
+    EXPECT_TRUE(u32_reader.IsEOF());
+
+    BufferReader i32_reader{Slice{data}};
+    EXPECT_FALSE(i32_reader.IsEOF());
+    auto i32 = i32_reader.Int<int32_t>(Endianness::kBig);
     EXPECT_EQ(i32, 0x10203040);
+    EXPECT_TRUE(i32_reader.IsEOF());
 }
 
 TEST(BufferReaderTest, IntegerBigEndian_TooShort) {
     auto data = Data(0x10, 0x20);
-    auto u32 = BufferReader{Slice{data}}.Int<uint32_t>(Endianness::kBig);
+
+    BufferReader u32_reader{Slice{data}};
+    EXPECT_FALSE(u32_reader.IsEOF());
+    auto u32 = u32_reader.Int<uint32_t>(Endianness::kBig);
     EXPECT_NE(u32, Success);
-    auto i32 = BufferReader{Slice{data}}.Int<int32_t>(Endianness::kBig);
+    EXPECT_TRUE(u32_reader.IsEOF());
+
+    BufferReader i32_reader{Slice{data}};
+    EXPECT_FALSE(i32_reader.IsEOF());
+    auto i32 = i32_reader.Int<int32_t>(Endianness::kBig);
     EXPECT_NE(i32, Success);
+    EXPECT_TRUE(i32_reader.IsEOF());
 }
 
 TEST(BufferReaderTest, IntegerLittleEndian) {
     auto data = Data(0x10, 0x20, 0x30, 0x40);
-    auto u32 = BufferReader{Slice{data}}.Int<uint32_t>(Endianness::kLittle);
+
+    BufferReader u32_reader{Slice{data}};
+    EXPECT_FALSE(u32_reader.IsEOF());
+    auto u32 = u32_reader.Int<uint32_t>(Endianness::kLittle);
     EXPECT_EQ(u32, 0x40302010u);
-    auto i32 = BufferReader{Slice{data}}.Int<int32_t>(Endianness::kLittle);
+    EXPECT_TRUE(u32_reader.IsEOF());
+
+    BufferReader i32_reader{Slice{data}};
+    EXPECT_FALSE(i32_reader.IsEOF());
+    auto i32 = i32_reader.Int<int32_t>(Endianness::kLittle);
     EXPECT_EQ(i32, 0x40302010);
+    EXPECT_TRUE(i32_reader.IsEOF());
 }
 
 TEST(BufferReaderTest, IntegerLittleEndian_TooShort) {
     auto data = Data(0x30, 0x40);
-    auto u32 = BufferReader{Slice{data}}.Int<uint32_t>(Endianness::kLittle);
+
+    BufferReader u32_reader{Slice{data}};
+    EXPECT_FALSE(u32_reader.IsEOF());
+    auto u32 = u32_reader.Int<uint32_t>(Endianness::kLittle);
     EXPECT_NE(u32, Success);
-    auto i32 = BufferReader{Slice{data}}.Int<int32_t>(Endianness::kLittle);
+    EXPECT_TRUE(u32_reader.IsEOF());
+
+    BufferReader i32_reader{Slice{data}};
+    EXPECT_FALSE(i32_reader.IsEOF());
+    auto i32 = i32_reader.Int<int32_t>(Endianness::kLittle);
     EXPECT_NE(i32, Success);
+    EXPECT_TRUE(i32_reader.IsEOF());
 }
 
 TEST(BufferReaderTest, Float) {
     auto data = Data(0x00, 0x00, 0x08, 0x41);
-    auto f32 = BufferReader{Slice{data}}.Float<float>();
+
+    BufferReader f32_reader{Slice{data}};
+    EXPECT_FALSE(f32_reader.IsEOF());
+    auto f32 = f32_reader.Float<float>();
     ASSERT_EQ(f32, Success);
     EXPECT_EQ(f32.Get(), 8.5f);
+    EXPECT_TRUE(f32_reader.IsEOF());
 }
 
 TEST(BufferReaderTest, Float_TooShort) {
     auto data = Data(0x08, 0x41);
-    auto f32 = BufferReader{Slice{data}}.Float<float>();
+
+    BufferReader f32_reader{Slice{data}};
+    EXPECT_FALSE(f32_reader.IsEOF());
+    auto f32 = f32_reader.Float<float>();
     EXPECT_NE(f32, Success);
+    EXPECT_TRUE(f32_reader.IsEOF());
 }
 
 }  // namespace