Import Tint changes from Dawn

Changes:
  - 4b452dc88a133cd21bd0af9287e7267145a13e6b [tint][core] Remove UnwrapRef() calls from intrinsic::Tab... by Ben Clayton <bclayton@google.com>
  - b91578ac795715992d291da26f614b6ce3b106bf Fixup remapped binding group for MSL options. by dan sinclair <dsinclair@chromium.org>
  - 6b718aa2b08bffcb9bf4ef4c2d1fbc04fac32317 [tint][ir] Fix unittests that used invalid binary-ops by Ben Clayton <bclayton@google.com>
  - 5ae7a137676a258a00d699b1e22bdede68f1c430 [tint][ir] Rename shiftl -> shl, shiftr -> shr by Ben Clayton <bclayton@google.com>
  - 30404595c85eea98d231b7c68e1f21a24cc03167 [tint][utils] Add bytes::Decoder support for optional and... by Ben Clayton <bclayton@google.com>
  - ebaf1174d90011de3724427a4ea0ef3c6e7d9e9d Fix syntax tree build. by dan sinclair <dsinclair@chromium.org>
  - ffefb48bb51ff8e5445d1efa11b920e74874125c [tint][core] Add MemoryView type by Ben Clayton <bclayton@google.com>
  - 8962cdd9051f3569b8ec5d7c44ff524fb9054e61 [tint][utils] Add TINT_REFLECT_ENUM_RANGE by Ben Clayton <bclayton@google.com>
  - 10c56f6efad017ac4fbee7e94c885a18488db635 [tint][ir] Serialize DepthMultisampleTexture types by Ben Clayton <bclayton@google.com>
  - a35b8d46adae9a2b95be39f1a30904309c6b0c00 [tint][ir] Serialize Unreachable instruction by Ben Clayton <bclayton@google.com>
  - 4c87ddf16dc7a506a015f7111c5572b8da3d7bee [tint][ir] Serialize MultisampledTexture types by Ben Clayton <bclayton@google.com>
  - fc24576d41ffb0e027d99f6ac6872d6773ce05d3 [tint][ir] Serialize Bitcast instructions by Ben Clayton <bclayton@google.com>
  - 07177d5d9d7a348b481eb32adc9fed2056950b70 [tint][ir] Serialize ExternalTexture by Ben Clayton <bclayton@google.com>
  - 7f4129354f1c08649ca8600c96e356283a86bf5c [tint][ir] Serialize more function return attributes by Ben Clayton <bclayton@google.com>
  - 3b0e5df0e510e43b9c6b58e3ce9a11d136aa41b4 [tint][ir] Remove Function::ReturnBuiltin, use core::Buil... by Ben Clayton <bclayton@google.com>
  - e8b7b02cbd6e20b5f247a88dabe12dc25e586a76 [tint][ir] Serialize function parameter attributes by Ben Clayton <bclayton@google.com>
  - 8c9411653c73c26265cc2175a608a20c444bcc22 Move new WGSL AST fuzzer. by dan sinclair <dsinclair@chromium.org>
  - f7c844b26baa8431462a4ddcd3c6602578842d2f msl: Explain side-effect annotation in preserved loops by David Neto <dneto@google.com>
  - a011704b230c4fe0c076b53e4a9eb9b14e57beb6 [tint][ir] Remove FunctionParam::Builtin, use core::Built... by Ben Clayton <bclayton@google.com>
  - c9115e1bd338325c4f91b5043077fa0e8e6a56bd [tint][type] Fix runtime-sized array implicit stride by Ben Clayton <bclayton@google.com>
  - 89274f7f65d20acb7292007f1f77858547b34ec9 [tint][utils] Remove boolean operators from Result by Ben Clayton <bclayton@google.com>
GitOrigin-RevId: 4b452dc88a133cd21bd0af9287e7267145a13e6b
Change-Id: Iffb2ccc6cb0de139e1e5c078f11e6fd26c0fe50a
Reviewed-on: https://dawn-review.googlesource.com/c/tint/+/168100
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/api/options/pixel_local.h b/src/tint/api/options/pixel_local.h
index d8f22ce..61e177b 100644
--- a/src/tint/api/options/pixel_local.h
+++ b/src/tint/api/options/pixel_local.h
@@ -50,12 +50,15 @@
     std::unordered_map<uint32_t, TexelFormat> attachment_formats;
 
     /// The bind group index of all pixel local storage attachments
-    uint32_t pixel_local_group_index;
+    uint32_t pixel_local_group_index = 0;
 
     /// Reflect the fields of this class so that it can be used by tint::ForeachField()
     TINT_REFLECT(attachments, attachment_formats, pixel_local_group_index);
 };
 
+/// Reflect valid value ranges for the PixelLocalOptions::TexelFormat enum.
+TINT_REFLECT_ENUM_RANGE(PixelLocalOptions::TexelFormat, kR32Sint, kR32Float);
+
 }  // namespace tint
 
 #endif  // SRC_TINT_API_OPTIONS_PIXEL_LOCAL_H_
diff --git a/src/tint/api/tint.cc b/src/tint/api/tint.cc
index b8b0c42..8a48cea 100644
--- a/src/tint/api/tint.cc
+++ b/src/tint/api/tint.cc
@@ -70,7 +70,7 @@
     // Register the Program printer. This is used for debugging purposes.
     tint::Program::printer = [](const tint::Program& program) {
         auto result = wgsl::writer::Generate(program, {});
-        if (!result) {
+        if (result != Success) {
             return result.Failure().reason.str();
         }
         return result->wgsl;
diff --git a/src/tint/cmd/bench/bench.cc b/src/tint/cmd/bench/bench.cc
index 9a05df4..f8736e1 100644
--- a/src/tint/cmd/bench/bench.cc
+++ b/src/tint/cmd/bench/bench.cc
@@ -127,7 +127,7 @@
     if (tint::HasSuffix(path, ".wgsl")) {
 #if TINT_BUILD_WGSL_READER
         auto data = ReadFile<uint8_t>(path);
-        if (!data) {
+        if (data != Success) {
             return data.Failure();
         }
         return tint::Source::File(path, std::string(data->begin(), data->end()));
@@ -143,7 +143,7 @@
 #else
 
         auto spirv = ReadFile<uint32_t>(path);
-        if (spirv) {
+        if (spirv == Success) {
             tint::spirv::reader::Options spirv_opts;
             spirv_opts.allow_non_uniform_derivatives = true;
             auto program = tint::spirv::reader::Read(spirv.Get(), spirv_opts);
@@ -151,7 +151,7 @@
                 return Failure{program.Diagnostics()};
             }
             auto result = tint::wgsl::writer::Generate(program, {});
-            if (!result) {
+            if (result != Success) {
                 return result.Failure();
             }
             return tint::Source::File(path, result->wgsl);
@@ -164,7 +164,7 @@
 
 Result<ProgramAndFile> LoadProgram(std::string name) {
     auto res = bench::LoadInputFile(name);
-    if (!res) {
+    if (res != Success) {
         return res.Failure();
     }
     auto file = std::make_unique<Source::File>(res.Get());
diff --git a/src/tint/cmd/common/helper.cc b/src/tint/cmd/common/helper.cc
index 4e2bc38..3e54e9f 100644
--- a/src/tint/cmd/common/helper.cc
+++ b/src/tint/cmd/common/helper.cc
@@ -116,7 +116,7 @@
 #if TINT_BUILD_WGSL_WRITER
         // Parse the SPIR-V binary to a core Tint IR module.
         auto result = tint::spirv::reader::ReadIR(data);
-        if (!result) {
+        if (result != Success) {
             std::cerr << "Failed to parse SPIR-V: " << result.Failure() << "\n";
             exit(1);
         }
@@ -164,7 +164,7 @@
 #if TINT_BUILD_WGSL_WRITER
     tint::wgsl::writer::Options options;
     auto result = tint::wgsl::writer::Generate(program, options);
-    if (result) {
+    if (result == Success) {
         out << std::endl << result->wgsl << std::endl;
     } else {
         out << result.Failure() << std::endl;
diff --git a/src/tint/cmd/fuzz/ir/fuzz.cc b/src/tint/cmd/fuzz/ir/fuzz.cc
index 8934bf2..5fa170f 100644
--- a/src/tint/cmd/fuzz/ir/fuzz.cc
+++ b/src/tint/cmd/fuzz/ir/fuzz.cc
@@ -76,11 +76,11 @@
             }
 
             auto ir = tint::wgsl::reader::ProgramToLoweredIR(src);
-            if (!ir) {
+            if (ir != Success) {
                 return;
             }
 
-            if (auto val = core::ir::Validate(ir.Get()); !val) {
+            if (auto val = core::ir::Validate(ir.Get()); val != Success) {
                 TINT_ICE() << val.Failure();
                 return;
             }
diff --git a/src/tint/cmd/fuzz/ir/fuzz.h b/src/tint/cmd/fuzz/ir/fuzz.h
index 36d8c02..fa1ffd0 100644
--- a/src/tint/cmd/fuzz/ir/fuzz.h
+++ b/src/tint/cmd/fuzz/ir/fuzz.h
@@ -57,7 +57,8 @@
                     return;
                 }
                 bytes::BufferReader reader{data};
-                if (auto data_args = bytes::Decode<std::tuple<std::decay_t<ARGS>...>>(reader)) {
+                if (auto data_args = bytes::Decode<std::tuple<std::decay_t<ARGS>...>>(reader);
+                    data_args == Success) {
                     auto all_args =
                         std::tuple_cat(std::tuple<core::ir::Module&>{module}, data_args.Get());
                     std::apply(*fn, all_args);
diff --git a/src/tint/cmd/fuzz/wgsl/BUILD.cmake b/src/tint/cmd/fuzz/wgsl/BUILD.cmake
index 4da9274..b0546c5 100644
--- a/src/tint/cmd/fuzz/wgsl/BUILD.cmake
+++ b/src/tint/cmd/fuzz/wgsl/BUILD.cmake
@@ -88,7 +88,7 @@
 
 if(TINT_BUILD_WGSL_WRITER)
   tint_target_add_dependencies(tint_cmd_fuzz_wgsl_fuzz_cmd fuzz_cmd
-    tint_lang_wgsl_writer_ast_printer_fuzz
+    tint_lang_wgsl_writer_fuzz
   )
 endif(TINT_BUILD_WGSL_WRITER)
 
diff --git a/src/tint/cmd/fuzz/wgsl/BUILD.gn b/src/tint/cmd/fuzz/wgsl/BUILD.gn
index f4bec4d..5dc14cb 100644
--- a/src/tint/cmd/fuzz/wgsl/BUILD.gn
+++ b/src/tint/cmd/fuzz/wgsl/BUILD.gn
@@ -122,7 +122,7 @@
     }
 
     if (tint_build_wgsl_writer) {
-      deps += [ "${tint_src_dir}/lang/wgsl/writer/ast_printer:fuzz" ]
+      deps += [ "${tint_src_dir}/lang/wgsl/writer:fuzz" ]
     }
   }
 }
diff --git a/src/tint/cmd/fuzz/wgsl/fuzz.h b/src/tint/cmd/fuzz/wgsl/fuzz.h
index da2efe4..d54b3ec 100644
--- a/src/tint/cmd/fuzz/wgsl/fuzz.h
+++ b/src/tint/cmd/fuzz/wgsl/fuzz.h
@@ -54,7 +54,8 @@
                     return;
                 }
                 bytes::BufferReader reader{data};
-                if (auto data_args = bytes::Decode<std::tuple<std::decay_t<ARGS>...>>(reader)) {
+                auto data_args = bytes::Decode<std::tuple<std::decay_t<ARGS>...>>(reader);
+                if (data_args == Success) {
                     auto all_args =
                         std::tuple_cat(std::tuple<const Program&>{program}, data_args.Get());
                     std::apply(*fn, all_args);
diff --git a/src/tint/cmd/fuzz/wgsl/main_fuzz.cc b/src/tint/cmd/fuzz/wgsl/main_fuzz.cc
index 1edcd04..8fa2c0f 100644
--- a/src/tint/cmd/fuzz/wgsl/main_fuzz.cc
+++ b/src/tint/cmd/fuzz/wgsl/main_fuzz.cc
@@ -75,7 +75,7 @@
 
     tint::cli::ParseOptions parse_opts;
     parse_opts.ignore_unknown = true;
-    if (auto res = opts.Parse(arguments, parse_opts); !res) {
+    if (auto res = opts.Parse(arguments, parse_opts); res != tint::Success) {
         show_help();
         std::cerr << res.Failure();
         return 0;
diff --git a/src/tint/cmd/loopy/main.cc b/src/tint/cmd/loopy/main.cc
index 6fcd4ac..ccd8b23 100644
--- a/src/tint/cmd/loopy/main.cc
+++ b/src/tint/cmd/loopy/main.cc
@@ -209,7 +209,7 @@
     tint::spirv::writer::Options gen_options;
     gen_options.bindings = tint::spirv::writer::GenerateBindings(program);
     auto result = tint::spirv::writer::Generate(program, gen_options);
-    if (!result) {
+    if (result != tint::Success) {
         tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
@@ -229,7 +229,7 @@
 #if TINT_BUILD_WGSL_WRITER
     tint::wgsl::writer::Options gen_options;
     auto result = tint::wgsl::writer::Generate(program, gen_options);
-    if (!result) {
+    if (result != tint::Success) {
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
     }
@@ -266,7 +266,7 @@
     gen_options.array_length_from_uniform.bindpoint_to_size_index.emplace(tint::BindingPoint{0, 1},
                                                                           1);
     auto result = tint::msl::writer::Generate(*input_program, gen_options);
-    if (!result) {
+    if (result != tint::Success) {
         tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
@@ -285,7 +285,7 @@
     gen_options.external_texture_options.bindings_map =
         tint::cmd::GenerateExternalTextureBindings(program);
     auto result = tint::hlsl::writer::Generate(program, gen_options);
-    if (!result) {
+    if (result != tint::Success) {
         tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
@@ -308,7 +308,7 @@
     gen_options.external_texture_options.bindings_map =
         tint::cmd::GenerateExternalTextureBindings(program);
     auto result = tint::glsl::writer::Generate(program, gen_options, "");
-    if (result) {
+    if (result == tint::Success) {
         tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
@@ -401,7 +401,7 @@
         }
         for (uint32_t i = 0; i < loop_count; ++i) {
             auto result = tint::wgsl::reader::ProgramToIR(info.program);
-            if (!result) {
+            if (result != tint::Success) {
                 std::cerr << "Failed to build IR from program: " << result.Failure() << std::endl;
             }
         }
@@ -448,7 +448,7 @@
                 return 1;
         }
     }
-    if (!success) {
+    if (success) {
         return 1;
     }
 
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index 57aaa08..4096b0e 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -422,7 +422,7 @@
     };
 
     auto result = options.Parse(arguments);
-    if (!result) {
+    if (result != tint::Success) {
         std::cerr << result.Failure() << "\n";
         show_usage();
         return false;
@@ -440,7 +440,7 @@
                 return false;
             }
             auto value = tint::strconv::ParseNumber<double>(parts[1]);
-            if (!value) {
+            if (value != tint::Success) {
                 std::cerr << "invalid override value: " << parts[1];
                 return false;
             }
@@ -456,13 +456,13 @@
             return false;
         }
         auto group = tint::strconv::ParseUint32(binding_points[0]);
-        if (!group) {
+        if (group != tint::Success) {
             std::cerr << "Invalid group for " << hlsl_rc_bp.name << ": " << binding_points[0]
                       << "\n";
             return false;
         }
         auto binding = tint::strconv::ParseUint32(binding_points[1]);
-        if (!binding) {
+        if (binding != tint::Success) {
             std::cerr << "Invalid binding for " << hlsl_rc_bp.name << ": " << binding_points[1]
                       << "\n";
             return false;
@@ -480,13 +480,13 @@
                 return false;
             }
             auto member_index = tint::strconv::ParseUint32(values[0]);
-            if (!member_index) {
+            if (member_index != tint::Success) {
                 std::cerr << "Invalid member index for " << pixel_local_attachments.name << ": "
                           << values[0] << "\n";
                 return false;
             }
             auto attachment_index = tint::strconv::ParseUint32(values[1]);
-            if (!attachment_index) {
+            if (attachment_index != tint::Success) {
                 std::cerr << "Invalid attachment index for " << pixel_local_attachments.name << ": "
                           << values[1] << "\n";
                 return false;
@@ -510,7 +510,7 @@
                 return false;
             }
             auto member_index = tint::strconv::ParseUint32(values[0]);
-            if (!member_index) {
+            if (member_index != tint::Success) {
                 std::cerr << "Invalid member index for " << pixel_local_attachment_formats.name
                           << ": " << values[0] << std::endl;
                 return false;
@@ -645,7 +645,7 @@
     if (options.use_ir) {
         // Convert the AST program to an IR module.
         auto ir = tint::wgsl::reader::ProgramToLoweredIR(program);
-        if (!ir) {
+        if (ir != tint::Success) {
             std::cerr << "Failed to generate IR: " << ir << "\n";
             return false;
         }
@@ -654,7 +654,7 @@
         result = tint::spirv::writer::Generate(program, gen_options);
     }
 
-    if (!result) {
+    if (result != tint::Success) {
         tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << "\n";
         return false;
@@ -707,7 +707,7 @@
     // TODO(jrprice): Provide a way for the user to set non-default options.
     tint::wgsl::writer::Options gen_options;
     auto result = tint::wgsl::writer::Generate(program, gen_options);
-    if (!result) {
+    if (result != tint::Success) {
         std::cerr << "Failed to generate: " << result.Failure() << "\n";
         return false;
     }
@@ -787,7 +787,7 @@
     if (options.use_ir) {
         // Convert the AST program to an IR module.
         auto ir = tint::wgsl::reader::ProgramToLoweredIR(program);
-        if (!ir) {
+        if (ir != tint::Success) {
             std::cerr << "Failed to generate IR: " << ir << "\n";
             return false;
         }
@@ -796,7 +796,7 @@
         result = tint::msl::writer::Generate(*input_program, gen_options);
     }
 
-    if (!result) {
+    if (result != tint::Success) {
         tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << "\n";
         return false;
@@ -868,7 +868,7 @@
     gen_options.root_constant_binding_point = options.hlsl_root_constant_binding_point;
     gen_options.pixel_local_options = options.pixel_local_options;
     auto result = tint::hlsl::writer::Generate(program, gen_options);
-    if (!result) {
+    if (result != tint::Success) {
         tint::cmd::PrintWGSL(std::cerr, program);
         std::cerr << "Failed to generate: " << result.Failure() << std::endl;
         return false;
@@ -997,7 +997,7 @@
         textureBuiltinsFromUniform.ubo_binding = {kMaxBindGroups, 0u};
         gen_options.texture_builtins_from_uniform = std::move(textureBuiltinsFromUniform);
         auto result = tint::glsl::writer::Generate(prg, gen_options, entry_point_name);
-        if (!result) {
+        if (result != tint::Success) {
             tint::cmd::PrintWGSL(std::cerr, prg);
             std::cerr << "Failed to generate: " << result.Failure() << "\n";
             return false;
@@ -1018,7 +1018,7 @@
             return false;
 #else
             auto val = tint::glsl::validate::Validate(result->glsl, result->entry_points);
-            if (!val) {
+            if (val != tint::Success) {
                 std::cerr << val.Failure();
                 return false;
             }
@@ -1107,8 +1107,8 @@
                      std::cerr << "empty override name\n";
                      return false;
                  }
-                 if (auto num =
-                         tint::strconv::ParseNumber<decltype(tint::OverrideId::value)>(name)) {
+                 if (auto num = tint::strconv::ParseNumber<decltype(tint::OverrideId::value)>(name);
+                     num == tint::Success) {
                      tint::OverrideId id{num.Get()};
                      values.emplace(id, value);
                  } else {
@@ -1169,7 +1169,7 @@
         tint::wgsl::writer::Options gen_options;
         gen_options.use_syntax_tree_writer = true;
         auto result = tint::wgsl::writer::Generate(info.program, gen_options);
-        if (!result) {
+        if (result != tint::Success) {
             std::cerr << "Failed to dump AST: " << result.Failure() << "\n";
         } else {
             std::cout << result->wgsl << "\n";
@@ -1180,7 +1180,7 @@
 #if TINT_BUILD_WGSL_READER
     if (options.dump_ir) {
         auto result = tint::wgsl::reader::ProgramToLoweredIR(info.program);
-        if (!result) {
+        if (result != tint::Success) {
             std::cerr << "Failed to build IR from program: " << result.Failure() << "\n";
         } else {
             auto mod = result.Move();
diff --git a/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc b/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc
index 9a12d9b..cdb0491 100644
--- a/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc
+++ b/src/tint/fuzzers/tint_ast_fuzzer/fuzzer.cc
@@ -79,7 +79,7 @@
     }
 
     auto result = wgsl::writer::Generate(program, wgsl::writer::Options());
-    if (!result) {
+    if (result != Success) {
         std::cout << "Can't generate WGSL for a valid tint::Program:" << std::endl
                   << result.Failure() << std::endl;
         return 0;
diff --git a/src/tint/fuzzers/tint_common_fuzzer.cc b/src/tint/fuzzers/tint_common_fuzzer.cc
index be08086..99f50c9 100644
--- a/src/tint/fuzzers/tint_common_fuzzer.cc
+++ b/src/tint/fuzzers/tint_common_fuzzer.cc
@@ -344,7 +344,7 @@
             }
 
             auto result = spirv::writer::Generate(program, options_spirv_);
-            if (result) {
+            if (result == Success) {
                 generated_spirv_ = std::move(result->spirv);
 
                 if (!SPIRVToolsValidationCheck(program, generated_spirv_)) {
diff --git a/src/tint/lang/core/constant/eval.cc b/src/tint/lang/core/constant/eval.cc
index 7a83d71..d86e0a1 100644
--- a/src/tint/lang/core/constant/eval.cc
+++ b/src/tint/lang/core/constant/eval.cc
@@ -273,7 +273,7 @@
         } else if constexpr (std::is_same_v<FROM, bool>) {
             // [bool -> x]
             return ctx.mgr.Get<Scalar<TO>>(target_ty, TO(scalar->value ? 1 : 0));
-        } else if (auto conv = CheckedConvert<TO>(scalar->value)) {
+        } else if (auto conv = CheckedConvert<TO>(scalar->value); conv == Success) {
             // Conversion success
             return ctx.mgr.Get<Scalar<TO>>(target_ty, conv.Get());
             // --- Below this point are the failure cases ---
@@ -497,7 +497,8 @@
     Vector<const Value*, 8> els;
     els.Reserve(n);
     for (uint32_t i = 0; i < n; i++) {
-        if (auto el = TransformElements(mgr, composite_el_ty, f, index + i, cs->Index(i)...)) {
+        if (auto el = TransformElements(mgr, composite_el_ty, f, index + i, cs->Index(i)...);
+            el == Success) {
             els.Push(el.Get());
         } else {
             return el.Failure();
@@ -525,7 +526,8 @@
     Vector<const Value*, 8> els;
     els.Reserve(n);
     for (uint32_t i = 0; i < n; i++) {
-        if (auto el = TransformUnaryElements(mgr, composite_el_ty, f, c0->Index(i))) {
+        if (auto el = TransformUnaryElements(mgr, composite_el_ty, f, c0->Index(i));
+            el == Success) {
             els.Push(el.Get());
         } else {
             return el.Failure();
@@ -556,8 +558,8 @@
     Vector<const Value*, 8> els;
     els.Reserve(n);
     for (uint32_t i = 0; i < n; i++) {
-        if (auto el =
-                TransformBinaryElements(mgr, composite_el_ty, f, c0->Index(i), c1->Index(i))) {
+        if (auto el = TransformBinaryElements(mgr, composite_el_ty, f, c0->Index(i), c1->Index(i));
+            el == Success) {
             els.Push(el.Get());
         } else {
             return el.Failure();
@@ -592,7 +594,8 @@
             return (num_elems == 1) ? c : c->Index(i);
         };
         if (auto el = TransformBinaryDifferingArityElements(
-                mgr, element_ty, f, nested_or_self(c0, n0), nested_or_self(c1, n1))) {
+                mgr, element_ty, f, nested_or_self(c0, n0), nested_or_self(c1, n1));
+            el == Success) {
             els.Push(el.Get());
         } else {
             return el.Failure();
@@ -624,7 +627,8 @@
     els.Reserve(n);
     for (uint32_t i = 0; i < n; i++) {
         if (auto el = TransformTernaryElements(mgr, composite_el_ty, f, c0->Index(i), c1->Index(i),
-                                               c2->Index(i))) {
+                                               c2->Index(i));
+            el == Success) {
             els.Push(el.Get());
         } else {
             return el.Failure();
@@ -840,15 +844,15 @@
                                               NumberT b1,
                                               NumberT b2) {
     auto r1 = Mul(source, a1, b1);
-    if (!r1) {
+    if (r1 != Success) {
         return error;
     }
     auto r2 = Mul(source, a2, b2);
-    if (!r2) {
+    if (r2 != Success) {
         return error;
     }
     auto r = Add(source, r1.Get(), r2.Get());
-    if (!r) {
+    if (r != Success) {
         return error;
     }
     return r;
@@ -863,23 +867,23 @@
                                               NumberT b2,
                                               NumberT b3) {
     auto r1 = Mul(source, a1, b1);
-    if (!r1) {
+    if (r1 != Success) {
         return error;
     }
     auto r2 = Mul(source, a2, b2);
-    if (!r2) {
+    if (r2 != Success) {
         return error;
     }
     auto r3 = Mul(source, a3, b3);
-    if (!r3) {
+    if (r3 != Success) {
         return error;
     }
     auto r = Add(source, r1.Get(), r2.Get());
-    if (!r) {
+    if (r != Success) {
         return error;
     }
     r = Add(source, r.Get(), r3.Get());
-    if (!r) {
+    if (r != Success) {
         return error;
     }
     return r;
@@ -896,31 +900,31 @@
                                               NumberT b3,
                                               NumberT b4) {
     auto r1 = Mul(source, a1, b1);
-    if (!r1) {
+    if (r1 != Success) {
         return error;
     }
     auto r2 = Mul(source, a2, b2);
-    if (!r2) {
+    if (r2 != Success) {
         return error;
     }
     auto r3 = Mul(source, a3, b3);
-    if (!r3) {
+    if (r3 != Success) {
         return error;
     }
     auto r4 = Mul(source, a4, b4);
-    if (!r4) {
+    if (r4 != Success) {
         return error;
     }
     auto r = Add(source, r1.Get(), r2.Get());
-    if (!r) {
+    if (r != Success) {
         return error;
     }
     r = Add(source, r.Get(), r3.Get());
-    if (!r) {
+    if (r != Success) {
         return error;
     }
     r = Add(source, r.Get(), r4.Get());
-    if (!r) {
+    if (r != Success) {
         return error;
     }
     return r;
@@ -940,15 +944,15 @@
     // a * d - c * b
 
     auto r1 = Mul(source, a, d);
-    if (!r1) {
+    if (r1 != Success) {
         return error;
     }
     auto r2 = Mul(source, c, b);
-    if (!r2) {
+    if (r2 != Success) {
         return error;
     }
     auto r = Sub(source, r1.Get(), r2.Get());
-    if (!r) {
+    if (r != Success) {
         return error;
     }
     return r;
@@ -975,31 +979,31 @@
     //   | f i |     | c i |     | c f |
 
     auto det1 = Det2(source, e, f, h, i);
-    if (!det1) {
+    if (det1 != Success) {
         return error;
     }
     auto a_det1 = Mul(source, a, det1.Get());
-    if (!a_det1) {
+    if (a_det1 != Success) {
         return error;
     }
     auto det2 = Det2(source, b, c, h, i);
-    if (!det2) {
+    if (det2 != Success) {
         return error;
     }
     auto d_det2 = Mul(source, d, det2.Get());
-    if (!d_det2) {
+    if (d_det2 != Success) {
         return error;
     }
     auto det3 = Det2(source, b, c, e, f);
-    if (!det3) {
+    if (det3 != Success) {
         return error;
     }
     auto g_det3 = Mul(source, g, det3.Get());
-    if (!g_det3) {
+    if (g_det3 != Success) {
         return error;
     }
     auto r = Sub(source, a_det1.Get(), d_det2.Get());
-    if (!r) {
+    if (r != Success) {
         return error;
     }
     return Add(source, r.Get(), g_det3.Get());
@@ -1035,43 +1039,43 @@
     //   | h l p |     | d l p |     | d h p |     | d h l |
 
     auto det1 = Det3(source, f, g, h, j, k, l, n, o, p);
-    if (!det1) {
+    if (det1 != Success) {
         return error;
     }
     auto a_det1 = Mul(source, a, det1.Get());
-    if (!a_det1) {
+    if (a_det1 != Success) {
         return error;
     }
     auto det2 = Det3(source, b, c, d, j, k, l, n, o, p);
-    if (!det2) {
+    if (det2 != Success) {
         return error;
     }
     auto e_det2 = Mul(source, e, det2.Get());
-    if (!e_det2) {
+    if (e_det2 != Success) {
         return error;
     }
     auto det3 = Det3(source, b, c, d, f, g, h, n, o, p);
-    if (!det3) {
+    if (det3 != Success) {
         return error;
     }
     auto i_det3 = Mul(source, i, det3.Get());
-    if (!i_det3) {
+    if (i_det3 != Success) {
         return error;
     }
     auto det4 = Det3(source, b, c, d, f, g, h, j, k, l);
-    if (!det4) {
+    if (det4 != Success) {
         return error;
     }
     auto m_det4 = Mul(source, m, det4.Get());
-    if (!m_det4) {
+    if (m_det4 != Success) {
         return error;
     }
     auto r = Sub(source, a_det1.Get(), e_det2.Get());
-    if (!r) {
+    if (r != Success) {
         return error;
     }
     r = Add(source, r.Get(), i_det3.Get());
-    if (!r) {
+    if (r != Success) {
         return error;
     }
     return Sub(source, r.Get(), m_det4.Get());
@@ -1092,7 +1096,7 @@
 
 auto Eval::SqrtFunc(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto v) -> Eval::Result {
-        if (auto r = Sqrt(source, v)) {
+        if (auto r = Sqrt(source, v); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1117,7 +1121,7 @@
 
 auto Eval::ClampFunc(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto e, auto low, auto high) -> Eval::Result {
-        if (auto r = Clamp(source, e, low, high)) {
+        if (auto r = Clamp(source, e, low, high); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1126,7 +1130,7 @@
 
 auto Eval::AddFunc(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto a1, auto a2) -> Eval::Result {
-        if (auto r = Add(source, a1, a2)) {
+        if (auto r = Add(source, a1, a2); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1135,7 +1139,7 @@
 
 auto Eval::SubFunc(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto a1, auto a2) -> Eval::Result {
-        if (auto r = Sub(source, a1, a2)) {
+        if (auto r = Sub(source, a1, a2); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1144,7 +1148,7 @@
 
 auto Eval::MulFunc(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto a1, auto a2) -> Eval::Result {
-        if (auto r = Mul(source, a1, a2)) {
+        if (auto r = Mul(source, a1, a2); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1153,7 +1157,7 @@
 
 auto Eval::DivFunc(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto a1, auto a2) -> Eval::Result {
-        if (auto r = Div(source, a1, a2)) {
+        if (auto r = Div(source, a1, a2); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1162,7 +1166,7 @@
 
 auto Eval::ModFunc(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto a1, auto a2) -> Eval::Result {
-        if (auto r = Mod(source, a1, a2)) {
+        if (auto r = Mod(source, a1, a2); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1171,7 +1175,7 @@
 
 auto Eval::Dot2Func(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto a1, auto a2, auto b1, auto b2) -> Eval::Result {
-        if (auto r = Dot2(source, a1, a2, b1, b2)) {
+        if (auto r = Dot2(source, a1, a2, b1, b2); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1180,7 +1184,7 @@
 
 auto Eval::Dot3Func(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto a1, auto a2, auto a3, auto b1, auto b2, auto b3) -> Eval::Result {
-        if (auto r = Dot3(source, a1, a2, a3, b1, b2, b3)) {
+        if (auto r = Dot3(source, a1, a2, a3, b1, b2, b3); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1190,7 +1194,7 @@
 auto Eval::Dot4Func(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto a1, auto a2, auto a3, auto a4, auto b1, auto b2, auto b3,
                auto b4) -> Eval::Result {
-        if (auto r = Dot4(source, a1, a2, a3, a4, b1, b2, b3, b4)) {
+        if (auto r = Dot4(source, a1, a2, a3, a4, b1, b2, b3, b4); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1235,7 +1239,7 @@
 
     // Evaluates to sqrt(e[0]^2 + e[1]^2 + ...) if T is a vector type.
     auto d = Dot(source, c0, c0);
-    if (!d) {
+    if (d != Success) {
         return error;
     }
     return Dispatch_fa_f32_f16(SqrtFunc(source, ty), d.Get());
@@ -1263,7 +1267,7 @@
 
 auto Eval::Det2Func(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto a, auto b, auto c, auto d) -> Eval::Result {
-        if (auto r = Det2(source, a, b, c, d)) {
+        if (auto r = Det2(source, a, b, c, d); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1273,7 +1277,7 @@
 auto Eval::Det3Func(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto a, auto b, auto c, auto d, auto e, auto f, auto g, auto h,
                auto i) -> Eval::Result {
-        if (auto r = Det3(source, a, b, c, d, e, f, g, h, i)) {
+        if (auto r = Det3(source, a, b, c, d, e, f, g, h, i); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1283,7 +1287,7 @@
 auto Eval::Det4Func(const Source& source, const core::type::Type* elem_ty) {
     return [=](auto a, auto b, auto c, auto d, auto e, auto f, auto g, auto h, auto i, auto j,
                auto k, auto l, auto m, auto n, auto o, auto p) -> Eval::Result {
-        if (auto r = Det4(source, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p)) {
+        if (auto r = Det4(source, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p); r == Success) {
             return CreateScalar(source, elem_ty, r.Get());
         }
         return error;
@@ -1504,31 +1508,35 @@
             dst_el_ty,
             [&](const core::type::U32*) {  //
                 auto r = CreateScalar(source, dst_el_ty, u32(v));
-                if (r) {
-                    els.Push(r.Get());
+                if (r != Success) {
+                    return false;
                 }
-                return r;
+                els.Push(r.Get());
+                return true;
             },
             [&](const core::type::I32*) {  //
                 auto r = CreateScalar(source, dst_el_ty, tint::Bitcast<i32>(v));
-                if (r) {
-                    els.Push(r.Get());
+                if (r != Success) {
+                    return false;
                 }
-                return r;
+                els.Push(r.Get());
+                return true;
             },
             [&](const core::type::F32*) {  //
                 auto r = CreateScalar(source, dst_el_ty, tint::Bitcast<f32>(v));
-                if (r) {
-                    els.Push(r.Get());
+                if (r != Success) {
+                    return false;
                 }
-                return r;
+                els.Push(r.Get());
+                return true;
             },
             [&](const core::type::F16*) {  //
                 auto r = CreateScalar(source, dst_el_ty, f16::FromBits(static_cast<uint16_t>(v)));
-                if (r) {
-                    els.Push(r.Get());
+                if (r != Success) {
+                    return false;
                 }
-                return r;
+                els.Push(r.Get());
+                return true;
             });
     };
 
@@ -1657,7 +1665,7 @@
     Vector<const Value*, 4> result;
     for (size_t i = 0; i < mat_ty->rows(); ++i) {
         auto r = dot(args[0], i, args[1]);  // matrix row i * vector
-        if (!r) {
+        if (r != Success) {
             return error;
         }
         result.Push(r.Get());
@@ -1707,7 +1715,7 @@
     Vector<const Value*, 4> result;
     for (size_t i = 0; i < mat_ty->columns(); ++i) {
         auto r = dot(args[0], args[1], i);  // vector * matrix col i
-        if (!r) {
+        if (r != Success) {
             return error;
         }
         result.Push(r.Get());
@@ -1766,7 +1774,7 @@
         Vector<const Value*, 4> col_vec;
         for (size_t r = 0; r < mat1_ty->rows(); ++r) {
             auto v = dot(mat1, r, mat2, c);  // mat1 row r * mat2 col c
-            if (!v) {
+            if (v != Success) {
                 return error;
             }
             col_vec.Push(v.Get());  // mat1 row r * mat2 col c
@@ -2403,15 +2411,15 @@
     auto* v2 = v->Index(2);
 
     auto x = Dispatch_fa_f32_f16(Det2Func(source, elem_ty), u1, u2, v1, v2);
-    if (!x) {
+    if (x != Success) {
         return error;
     }
     auto y = Dispatch_fa_f32_f16(Det2Func(source, elem_ty), v0, v2, u0, u2);
-    if (!y) {
+    if (y != Success) {
         return error;
     }
     auto z = Dispatch_fa_f32_f16(Det2Func(source, elem_ty), u0, u1, v0, v1);
-    if (!z) {
+    if (z != Success) {
         return error;
     }
 
@@ -2428,12 +2436,12 @@
 
             auto pi = kPi<T>;
             auto scale = Div(source, NumberT(180), NumberT(pi));
-            if (!scale) {
+            if (scale != Success) {
                 AddNote("when calculating degrees", source);
                 return error;
             }
             auto result = Mul(source, e, scale.Get());
-            if (!result) {
+            if (result != Success) {
                 AddNote("when calculating degrees", source);
                 return error;
             }
@@ -2474,7 +2482,7 @@
         return error;
     };
     auto r = calculate();
-    if (!r) {
+    if (r != Success) {
         AddNote("when calculating determinant", source);
     }
     return r;
@@ -2489,12 +2497,12 @@
     };
 
     auto minus = Minus(args[0]->Type(), args, source);
-    if (!minus) {
+    if (minus != Success) {
         return err();
     }
 
     auto len = Length(source, ty, minus.Get());
-    if (!len) {
+    if (len != Success) {
         return err();
     }
     return len;
@@ -2504,7 +2512,7 @@
                        VectorRef<const Value*> args,
                        const Source& source) {
     auto r = Dot(source, args[0], args[1]);
-    if (!r) {
+    if (r != Success) {
         AddNote("when calculating dot", source);
     }
     return r;
@@ -2652,7 +2660,7 @@
     auto* e2 = args[1];
     auto* e3 = args[2];
     auto r = Dot(source, e2, e3);
-    if (!r) {
+    if (r != Success) {
         AddNote("when calculating faceForward", source);
         return error;
     }
@@ -2756,12 +2764,12 @@
             };
 
             auto mul = Mul(source, e1, e2);
-            if (!mul) {
+            if (mul != Success) {
                 return err_msg();
             }
 
             auto val = Add(source, mul.Get(), e3);
-            if (!val) {
+            if (val != Success) {
                 return err_msg();
             }
             return CreateScalar(source, c1->Type(), val.Get());
@@ -2826,7 +2834,7 @@
         Vector<const Value*, 4> exp_els;
         for (uint32_t i = 0; i < vec->Width(); i++) {
             auto fe = scalar(arg->Index(i));
-            if (!fe.fract || !fe.exp) {
+            if (fe.fract != Success || fe.exp != Success) {
                 return error;
             }
             fract_els.Push(fe.fract.Get());
@@ -2840,7 +2848,7 @@
                                  });
     } else {
         auto fe = scalar(arg);
-        if (!fe.fract || !fe.exp) {
+        if (fe.fract != Success || fe.exp != Success) {
             return error;
         }
         return mgr.Composite(ty, Vector<const Value*, 2>{
@@ -2929,11 +2937,11 @@
             };
 
             auto s = Sqrt(source, e);
-            if (!s) {
+            if (s != Success) {
                 return err();
             }
             auto div = Div(source, NumberT(1), s.Get());
-            if (!div) {
+            if (div != Success) {
                 return err();
             }
 
@@ -2995,7 +3003,7 @@
                           VectorRef<const Value*> args,
                           const Source& source) {
     auto r = Length(source, ty, args[0]);
-    if (!r) {
+    if (r != Success) {
         AddNote("when calculating length", source);
     }
     return r;
@@ -3084,19 +3092,19 @@
             // Implement as `e1 * (1 - e3) + e2 * e3)` instead of as `e1 + e3 * (e2 - e1)` to avoid
             // float precision loss when e1 and e2 significantly differ in magnitude.
             auto one_sub_e3 = Sub(source, NumberT{1}, e3);
-            if (!one_sub_e3) {
+            if (one_sub_e3 != Success) {
                 return error;
             }
             auto e1_mul_one_sub_e3 = Mul(source, e1, one_sub_e3.Get());
-            if (!e1_mul_one_sub_e3) {
+            if (e1_mul_one_sub_e3 != Success) {
                 return error;
             }
             auto e2_mul_e3 = Mul(source, e2, e3);
-            if (!e2_mul_e3) {
+            if (e2_mul_e3 != Success) {
                 return error;
             }
             auto r = Add(source, e1_mul_one_sub_e3.Get(), e2_mul_e3.Get());
-            if (!r) {
+            if (r != Success) {
                 return error;
             }
             return CreateScalar(source, c0->Type(), r.Get());
@@ -3104,7 +3112,7 @@
         return Dispatch_fa_f32_f16(create, c0, c1);
     };
     auto r = TransformElements(mgr, ty, transform, 0, args[0], args[1]);
-    if (!r) {
+    if (r != Success) {
         AddNote("when calculating mix", source);
     }
     return r;
@@ -3128,13 +3136,15 @@
 
     Vector<const Value*, 2> fields;
 
-    if (auto fract = TransformUnaryElements(mgr, args[0]->Type(), transform_fract, args[0])) {
+    if (auto fract = TransformUnaryElements(mgr, args[0]->Type(), transform_fract, args[0]);
+        fract == Success) {
         fields.Push(fract.Get());
     } else {
         return error;
     }
 
-    if (auto whole = TransformUnaryElements(mgr, args[0]->Type(), transform_whole, args[0])) {
+    if (auto whole = TransformUnaryElements(mgr, args[0]->Type(), transform_whole, args[0]);
+        whole == Success) {
         fields.Push(whole.Get());
     } else {
         return error;
@@ -3148,7 +3158,7 @@
                              const Source& source) {
     auto* len_ty = ty->DeepestElement();
     auto len = Length(source, len_ty, args[0]);
-    if (!len) {
+    if (len != Success) {
         AddNote("when calculating normalize", source);
         return error;
     }
@@ -3169,7 +3179,7 @@
                                  const Source& source) {
     auto convert = [&](f32 val) -> tint::Result<uint32_t, Error> {
         auto conv = CheckedConvert<f16>(val);
-        if (!conv) {
+        if (conv != Success) {
             AddError(OverflowErrorMessage(val, "f16"), source);
             if (use_runtime_semantics_) {
                 return 0;
@@ -3183,12 +3193,12 @@
 
     auto* e = args[0];
     auto e0 = convert(e->Index(0)->ValueAs<f32>());
-    if (!e0) {
+    if (e0 != Success) {
         return error;
     }
 
     auto e1 = convert(e->Index(1)->ValueAs<f32>());
-    if (!e1) {
+    if (e1 != Success) {
         return error;
     }
 
@@ -3327,12 +3337,12 @@
 
             auto pi = kPi<T>;
             auto scale = Div(source, NumberT(pi), NumberT(180));
-            if (!scale) {
+            if (scale != Success) {
                 AddNote("when calculating radians", source);
                 return error;
             }
             auto result = Mul(source, e, scale.Get());
-            if (!result) {
+            if (result != Success) {
                 AddNote("when calculating radians", source);
                 return error;
             }
@@ -3356,7 +3366,7 @@
 
         // dot(e2, e1)
         auto dot_e2_e1 = Dot(source, e2, e1);
-        if (!dot_e2_e1) {
+        if (dot_e2_e1 != Success) {
             return error;
         }
 
@@ -3366,13 +3376,13 @@
             return CreateScalar(source, el_ty, NumberT{NumberT{2} * v});
         };
         auto dot_e2_e1_2 = Dispatch_fa_f32_f16(mul2, dot_e2_e1.Get());
-        if (!dot_e2_e1_2) {
+        if (dot_e2_e1_2 != Success) {
             return error;
         }
 
         // 2 * dot(e2, e1) * e2
         auto dot_e2_e1_2_e2 = Mul(source, ty, dot_e2_e1_2.Get(), e2);
-        if (!dot_e2_e1_2_e2) {
+        if (dot_e2_e1_2_e2 != Success) {
             return error;
         }
 
@@ -3380,7 +3390,7 @@
         return Sub(source, ty, e1, dot_e2_e1_2_e2.Get());
     };
     auto r = calculate();
-    if (!r) {
+    if (r != Success) {
         AddNote("when calculating reflect", source);
     }
     return r;
@@ -3396,23 +3406,23 @@
         using NumberT = decltype(e3);
         // let k = 1.0 - e3 * e3 * (1.0 - dot(e2, e1) * dot(e2, e1))
         auto e3_squared = Mul(source, e3, e3);
-        if (!e3_squared) {
+        if (e3_squared != Success) {
             return error;
         }
         auto dot_e2_e1_squared = Mul(source, dot_e2_e1, dot_e2_e1);
-        if (!dot_e2_e1_squared) {
+        if (dot_e2_e1_squared != Success) {
             return error;
         }
         auto r = Sub(source, NumberT(1), dot_e2_e1_squared.Get());
-        if (!r) {
+        if (r != Success) {
             return error;
         }
         r = Mul(source, e3_squared.Get(), r.Get());
-        if (!r) {
+        if (r != Success) {
             return error;
         }
         r = Sub(source, NumberT(1), r.Get());
-        if (!r) {
+        if (r != Success) {
             return error;
         }
         return CreateScalar(source, el_ty, r.Get());
@@ -3421,15 +3431,15 @@
     auto compute_e2_scale = [&](auto e3, auto dot_e2_e1, auto k) -> Eval::Result {
         // e3 * dot(e2, e1) + sqrt(k)
         auto sqrt_k = Sqrt(source, k);
-        if (!sqrt_k) {
+        if (sqrt_k != Success) {
             return error;
         }
         auto r = Mul(source, e3, dot_e2_e1);
-        if (!r) {
+        if (r != Success) {
             return error;
         }
         r = Add(source, r.Get(), sqrt_k.Get());
-        if (!r) {
+        if (r != Success) {
             return error;
         }
         return CreateScalar(source, el_ty, r.Get());
@@ -3447,13 +3457,13 @@
 
         // dot(e2, e1)
         auto dot_e2_e1 = Dot(source, e2, e1);
-        if (!dot_e2_e1) {
+        if (dot_e2_e1 != Success) {
             return error;
         }
 
         // let k = 1.0 - e3 * e3 * (1.0 - dot(e2, e1) * dot(e2, e1))
         auto k = Dispatch_fa_f32_f16(compute_k, e3, dot_e2_e1.Get());
-        if (!k) {
+        if (k != Success) {
             return error;
         }
 
@@ -3464,21 +3474,21 @@
 
         // Otherwise return the refraction vector e3 * e1 - (e3 * dot(e2, e1) + sqrt(k)) * e2
         auto e1_scaled = Mul(source, ty, e3, e1);
-        if (!e1_scaled) {
+        if (e1_scaled != Success) {
             return error;
         }
         auto e2_scale = Dispatch_fa_f32_f16(compute_e2_scale, e3, dot_e2_e1.Get(), k.Get());
-        if (!e2_scale) {
+        if (e2_scale != Success) {
             return error;
         }
         auto e2_scaled = Mul(source, ty, e2_scale.Get(), e2);
-        if (!e2_scaled) {
+        if (e2_scaled != Success) {
             return error;
         }
         return Sub(source, ty, e1_scaled.Get(), e2_scaled.Get());
     };
     auto r = calculate();
-    if (!r) {
+    if (r != Success) {
         AddNote("when calculating refract", source);
     }
     return r;
@@ -3653,12 +3663,12 @@
             // t = clamp((x - low) / (high - low), 0.0, 1.0)
             auto x_minus_low = Sub(source, x, low);
             auto high_minus_low = Sub(source, high, low);
-            if (!x_minus_low || !high_minus_low) {
+            if (x_minus_low != Success || high_minus_low != Success) {
                 return err();
             }
 
             auto div = Div(source, x_minus_low.Get(), high_minus_low.Get());
-            if (!div) {
+            if (div != Success) {
                 return err();
             }
 
@@ -3668,17 +3678,17 @@
             // result = t * t * (3.0 - 2.0 * t)
             auto t_times_t = Mul(source, t, t);
             auto t_times_2 = Mul(source, NumberT(2), t);
-            if (!t_times_t || !t_times_2) {
+            if (t_times_t != Success || t_times_2 != Success) {
                 return err();
             }
 
             auto three_minus_t_times_2 = Sub(source, NumberT(3), t_times_2.Get());
-            if (!three_minus_t_times_2) {
+            if (three_minus_t_times_2 != Success) {
                 return err();
             }
 
             auto result = Mul(source, t_times_t.Get(), three_minus_t_times_2.Get());
-            if (!result) {
+            if (result != Success) {
                 return err();
             }
             return CreateScalar(source, c0->Type(), result.Get());
@@ -3781,7 +3791,7 @@
     for (size_t i = 0; i < 2; ++i) {
         auto in = f16::FromBits(uint16_t((e >> (16 * i)) & 0x0000'ffff));
         auto val = CheckedConvert<f32>(in);
-        if (!val) {
+        if (val != Success) {
             AddError(OverflowErrorMessage(in, "f32"), source);
             if (use_runtime_semantics_) {
                 val = f32(0.f);
@@ -3790,7 +3800,7 @@
             }
         }
         auto el = CreateScalar(source, inner_ty, val.Get());
-        if (!el) {
+        if (el != Success) {
             return el;
         }
         els.Push(el.Get());
@@ -3810,7 +3820,7 @@
         auto val = f32(
             std::max(static_cast<float>(int16_t((e >> (16 * i)) & 0x0000'ffff)) / 32767.f, -1.f));
         auto el = CreateScalar(source, inner_ty, val);
-        if (!el) {
+        if (el != Success) {
             return el;
         }
         els.Push(el.Get());
@@ -3829,7 +3839,7 @@
     for (size_t i = 0; i < 2; ++i) {
         auto val = f32(static_cast<float>(uint16_t((e >> (16 * i)) & 0x0000'ffff)) / 65535.f);
         auto el = CreateScalar(source, inner_ty, val);
-        if (!el) {
+        if (el != Success) {
             return el;
         }
         els.Push(el.Get());
@@ -3849,7 +3859,7 @@
         auto val =
             f32(std::max(static_cast<float>(int8_t((e >> (8 * i)) & 0x0000'00ff)) / 127.f, -1.f));
         auto el = CreateScalar(source, inner_ty, val);
-        if (!el) {
+        if (el != Success) {
             return el;
         }
         els.Push(el.Get());
@@ -3868,7 +3878,7 @@
     for (size_t i = 0; i < 4; ++i) {
         auto val = f32(static_cast<float>(uint8_t((e >> (8 * i)) & 0x0000'00ff)) / 255.f);
         auto el = CreateScalar(source, inner_ty, val);
-        if (!el) {
+        if (el != Success) {
             return el;
         }
         els.Push(el.Get());
@@ -3882,7 +3892,7 @@
     auto transform = [&](const Value* c) -> Eval::Result {
         auto value = c->ValueAs<f32>();
         auto conv = CheckedConvert<f32>(f16(value));
-        if (!conv) {
+        if (conv != Success) {
             AddError(OverflowErrorMessage(value, "f16"), source);
             if (use_runtime_semantics_) {
                 return mgr.Zero(c->Type());
diff --git a/src/tint/lang/core/constant/eval_binary_op_test.cc b/src/tint/lang/core/constant/eval_binary_op_test.cc
index dfbed15..36ea805 100644
--- a/src/tint/lang/core/constant/eval_binary_op_test.cc
+++ b/src/tint/lang/core/constant/eval_binary_op_test.cc
@@ -83,7 +83,7 @@
 /// Prints Case to ostream
 static std::ostream& operator<<(std::ostream& o, const Case& c) {
     o << "lhs: " << c.lhs << ", rhs: " << c.rhs << ", expected: ";
-    if (c.expected) {
+    if (c.expected == Success) {
         auto& s = c.expected.Get();
         o << s.value;
     } else {
@@ -110,7 +110,7 @@
     auto* expr = create<ast::BinaryExpression>(Source{{12, 34}}, op, lhs_expr, rhs_expr);
     GlobalConst("C", expr);
 
-    if (c.expected) {
+    if (c.expected == Success) {
         ASSERT_TRUE(r()->Resolve()) << r()->error();
         auto expected_case = c.expected.Get();
         auto& expected = expected_case.value;
diff --git a/src/tint/lang/core/constant/eval_bitcast_test.cc b/src/tint/lang/core/constant/eval_bitcast_test.cc
index ac40178..8363a9e 100644
--- a/src/tint/lang/core/constant/eval_bitcast_test.cc
+++ b/src/tint/lang/core/constant/eval_bitcast_test.cc
@@ -46,7 +46,7 @@
 
 static std::ostream& operator<<(std::ostream& o, const Case& c) {
     o << "input: " << c.input;
-    if (c.expected) {
+    if (c.expected == Success) {
         o << ", expected: " << c.expected.Get().value;
     } else {
         o << ", expected failed bitcast to " << c.expected.Failure().create_ptrs;
@@ -72,7 +72,7 @@
 
     // Get the target type CreatePtrs
     builder::CreatePtrs target_create_ptrs;
-    if (expected) {
+    if (expected == tint::Success) {
         target_create_ptrs = expected.Get().value.create_ptrs;
     } else {
         target_create_ptrs = expected.Failure().create_ptrs;
@@ -87,7 +87,7 @@
 
     auto* target_sem_ty = target_create_ptrs.sem(*this);
 
-    if (expected) {
+    if (expected == tint::Success) {
         EXPECT_TRUE(r()->Resolve()) << r()->error();
 
         auto* sem = Sem().GetVal(expr);
diff --git a/src/tint/lang/core/constant/eval_builtin_test.cc b/src/tint/lang/core/constant/eval_builtin_test.cc
index a07f32b..f0a1e27 100644
--- a/src/tint/lang/core/constant/eval_builtin_test.cc
+++ b/src/tint/lang/core/constant/eval_builtin_test.cc
@@ -79,7 +79,7 @@
         o << a << ", ";
     }
     o << "expected: ";
-    if (c.expected) {
+    if (c.expected == Success) {
         auto s = c.expected.Get();
         if (s.values.Length() == 1) {
             o << s.values[0];
@@ -168,7 +168,7 @@
     auto* expr = Call(Source{{12, 34}}, core::str(builtin), std::move(args));
     GlobalConst("C", expr);
 
-    if (c.expected) {
+    if (c.expected == Success) {
         auto expected_case = c.expected.Get();
 
         ASSERT_TRUE(r()->Resolve()) << r()->error();
diff --git a/src/tint/lang/core/constant/eval_runtime_semantics_test.cc b/src/tint/lang/core/constant/eval_runtime_semantics_test.cc
index 618daa4..95798e9 100644
--- a/src/tint/lang/core/constant/eval_runtime_semantics_test.cc
+++ b/src/tint/lang/core/constant/eval_runtime_semantics_test.cc
@@ -51,7 +51,7 @@
     auto* a = constants.Get(AInt::Highest());
     auto* b = constants.Get(AInt(1));
     auto result = eval.Plus(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<AInt>(), 0);
     EXPECT_EQ(error(),
               R"(warning: '9223372036854775807 + 1' cannot be represented as 'abstract-int')");
@@ -61,7 +61,7 @@
     auto* a = constants.Get(AFloat::Highest());
     auto* b = constants.Get(AFloat::Highest());
     auto result = eval.Plus(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<AFloat>(), 0.f);
     EXPECT_EQ(
         error(),
@@ -72,7 +72,7 @@
     auto* a = constants.Get(f32::Highest());
     auto* b = constants.Get(f32::Highest());
     auto result = eval.Plus(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(
         error(),
@@ -83,7 +83,7 @@
     auto* a = constants.Get(AInt::Lowest());
     auto* b = constants.Get(AInt(1));
     auto result = eval.Minus(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<AInt>(), 0);
     EXPECT_EQ(error(),
               R"(warning: '-9223372036854775808 - 1' cannot be represented as 'abstract-int')");
@@ -93,7 +93,7 @@
     auto* a = constants.Get(AFloat::Lowest());
     auto* b = constants.Get(AFloat::Highest());
     auto result = eval.Minus(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<AFloat>(), 0.f);
     EXPECT_EQ(
         error(),
@@ -104,7 +104,7 @@
     auto* a = constants.Get(f32::Lowest());
     auto* b = constants.Get(f32::Highest());
     auto result = eval.Minus(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(
         error(),
@@ -115,7 +115,7 @@
     auto* a = constants.Get(AInt::Highest());
     auto* b = constants.Get(AInt(2));
     auto result = eval.Multiply(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<AInt>(), 0);
     EXPECT_EQ(error(),
               R"(warning: '9223372036854775807 * 2' cannot be represented as 'abstract-int')");
@@ -125,7 +125,7 @@
     auto* a = constants.Get(AFloat::Highest());
     auto* b = constants.Get(AFloat::Highest());
     auto result = eval.Multiply(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<AFloat>(), 0.f);
     EXPECT_EQ(
         error(),
@@ -136,7 +136,7 @@
     auto* a = constants.Get(f32::Highest());
     auto* b = constants.Get(f32::Highest());
     auto result = eval.Multiply(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(
         error(),
@@ -147,7 +147,7 @@
     auto* a = constants.Get(AInt(42));
     auto* b = constants.Get(AInt(0));
     auto result = eval.Divide(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<AInt>(), 42);
     EXPECT_EQ(error(), R"(warning: '42 / 0' cannot be represented as 'abstract-int')");
 }
@@ -156,7 +156,7 @@
     auto* a = constants.Get(i32(42));
     auto* b = constants.Get(i32(0));
     auto result = eval.Divide(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<i32>(), 42);
     EXPECT_EQ(error(), R"(warning: '42 / 0' cannot be represented as 'i32')");
 }
@@ -165,7 +165,7 @@
     auto* a = constants.Get(u32(42));
     auto* b = constants.Get(u32(0));
     auto result = eval.Divide(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<u32>(), 42);
     EXPECT_EQ(error(), R"(warning: '42 / 0' cannot be represented as 'u32')");
 }
@@ -174,7 +174,7 @@
     auto* a = constants.Get(AFloat(42));
     auto* b = constants.Get(AFloat(0));
     auto result = eval.Divide(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<AFloat>(), 42.f);
     EXPECT_EQ(error(), R"(warning: '42.0 / 0.0' cannot be represented as 'abstract-float')");
 }
@@ -183,7 +183,7 @@
     auto* a = constants.Get(f32(42));
     auto* b = constants.Get(f32(0));
     auto result = eval.Divide(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 42.f);
     EXPECT_EQ(error(), R"(warning: '42.0 / 0.0' cannot be represented as 'f32')");
 }
@@ -192,7 +192,7 @@
     auto* a = constants.Get(i32::Lowest());
     auto* b = constants.Get(i32(-1));
     auto result = eval.Divide(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<i32>(), i32::Lowest());
     EXPECT_EQ(error(), R"(warning: '-2147483648 / -1' cannot be represented as 'i32')");
 }
@@ -201,7 +201,7 @@
     auto* a = constants.Get(AInt(42));
     auto* b = constants.Get(AInt(0));
     auto result = eval.Modulo(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<AInt>(), 0);
     EXPECT_EQ(error(), R"(warning: '42 % 0' cannot be represented as 'abstract-int')");
 }
@@ -210,7 +210,7 @@
     auto* a = constants.Get(i32(42));
     auto* b = constants.Get(i32(0));
     auto result = eval.Modulo(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<i32>(), 0);
     EXPECT_EQ(error(), R"(warning: '42 % 0' cannot be represented as 'i32')");
 }
@@ -219,7 +219,7 @@
     auto* a = constants.Get(u32(42));
     auto* b = constants.Get(u32(0));
     auto result = eval.Modulo(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<u32>(), 0);
     EXPECT_EQ(error(), R"(warning: '42 % 0' cannot be represented as 'u32')");
 }
@@ -228,7 +228,7 @@
     auto* a = constants.Get(AFloat(42));
     auto* b = constants.Get(AFloat(0));
     auto result = eval.Modulo(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<AFloat>(), 0.f);
     EXPECT_EQ(error(), R"(warning: '42.0 % 0.0' cannot be represented as 'abstract-float')");
 }
@@ -237,7 +237,7 @@
     auto* a = constants.Get(f32(42));
     auto* b = constants.Get(f32(0));
     auto result = eval.Modulo(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(), R"(warning: '42.0 % 0.0' cannot be represented as 'f32')");
 }
@@ -246,7 +246,7 @@
     auto* a = constants.Get(i32::Lowest());
     auto* b = constants.Get(i32(-1));
     auto result = eval.Modulo(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<i32>(), 0);
     EXPECT_EQ(error(), R"(warning: '-2147483648 % -1' cannot be represented as 'i32')");
 }
@@ -255,7 +255,7 @@
     auto* a = constants.Get(AInt(0x0FFFFFFFFFFFFFFFll));
     auto* b = constants.Get(u32(9));
     auto result = eval.ShiftLeft(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<AInt>(), static_cast<AInt>(0x0FFFFFFFFFFFFFFFull << 9));
     EXPECT_EQ(error(), R"(warning: shift left operation results in sign change)");
 }
@@ -264,7 +264,7 @@
     auto* a = constants.Get(i32(0x0FFFFFFF));
     auto* b = constants.Get(u32(9));
     auto result = eval.ShiftLeft(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<i32>(), static_cast<i32>(0x0FFFFFFFu << 9));
     EXPECT_EQ(error(), R"(warning: shift left operation results in sign change)");
 }
@@ -273,7 +273,7 @@
     auto* a = constants.Get(i32(0x1));
     auto* b = constants.Get(u32(33));
     auto result = eval.ShiftLeft(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<i32>(), 2);
     EXPECT_EQ(
         error(),
@@ -284,7 +284,7 @@
     auto* a = constants.Get(u32(0x1));
     auto* b = constants.Get(u32(33));
     auto result = eval.ShiftLeft(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<u32>(), 2);
     EXPECT_EQ(
         error(),
@@ -295,7 +295,7 @@
     auto* a = constants.Get(i32(0x2));
     auto* b = constants.Get(u32(33));
     auto result = eval.ShiftRight(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<i32>(), 1);
     EXPECT_EQ(
         error(),
@@ -306,7 +306,7 @@
     auto* a = constants.Get(u32(0x2));
     auto* b = constants.Get(u32(33));
     auto result = eval.ShiftRight(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<u32>(), 1);
     EXPECT_EQ(
         error(),
@@ -316,7 +316,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Acos_F32_OutOfRange) {
     auto* a = constants.Get(f32(2));
     auto result = eval.acos(a->Type(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(),
               R"(warning: acos must be called with a value in the range [-1 .. 1] (inclusive))");
@@ -325,7 +325,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Acosh_F32_OutOfRange) {
     auto* a = constants.Get(f32(-1));
     auto result = eval.acosh(a->Type(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(), R"(warning: acosh must be called with a value >= 1.0)");
 }
@@ -333,7 +333,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Asin_F32_OutOfRange) {
     auto* a = constants.Get(f32(2));
     auto result = eval.asin(a->Type(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(),
               R"(warning: asin must be called with a value in the range [-1 .. 1] (inclusive))");
@@ -342,7 +342,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Atanh_F32_OutOfRange) {
     auto* a = constants.Get(f32(2));
     auto result = eval.atanh(a->Type(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(),
               R"(warning: atanh must be called with a value in the range (-1 .. 1) (exclusive))");
@@ -351,7 +351,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Exp_F32_Overflow) {
     auto* a = constants.Get(f32(1000));
     auto result = eval.exp(a->Type(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(), R"(warning: e^1000.0 cannot be represented as 'f32')");
 }
@@ -359,7 +359,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Exp2_F32_Overflow) {
     auto* a = constants.Get(f32(1000));
     auto result = eval.exp2(a->Type(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(), R"(warning: 2^1000.0 cannot be represented as 'f32')");
 }
@@ -369,7 +369,7 @@
     auto* offset = constants.Get(u32(24));
     auto* count = constants.Get(u32(16));
     auto result = eval.extractBits(a->Type(), Vector{a, offset, count}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<i32>(), 0x12);
     EXPECT_EQ(error(),
               R"(warning: 'offset + 'count' must be less than or equal to the bit width of 'e')");
@@ -380,7 +380,7 @@
     auto* offset = constants.Get(u32(24));
     auto* count = constants.Get(u32(16));
     auto result = eval.extractBits(a->Type(), Vector{a, offset, count}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<u32>(), 0x12);
     EXPECT_EQ(error(),
               R"(warning: 'offset + 'count' must be less than or equal to the bit width of 'e')");
@@ -392,7 +392,7 @@
     auto* offset = constants.Get(u32(24));
     auto* count = constants.Get(u32(16));
     auto result = eval.insertBits(a->Type(), Vector{a, b, offset, count}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<i32>(), 0x12345678);
     EXPECT_EQ(error(),
               R"(warning: 'offset + 'count' must be less than or equal to the bit width of 'e')");
@@ -404,7 +404,7 @@
     auto* offset = constants.Get(u32(24));
     auto* count = constants.Get(u32(16));
     auto result = eval.insertBits(a->Type(), Vector{a, b, offset, count}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<u32>(), 0x12345678);
     EXPECT_EQ(error(),
               R"(warning: 'offset + 'count' must be less than or equal to the bit width of 'e')");
@@ -413,7 +413,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, InverseSqrt_F32_OutOfRange) {
     auto* a = constants.Get(f32(-1));
     auto result = eval.inverseSqrt(a->Type(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(), R"(warning: inverseSqrt must be called with a value > 0)");
 }
@@ -422,7 +422,7 @@
     auto* a = constants.Get(f32(42.f));
     auto* b = constants.Get(f32(200));
     auto result = eval.ldexp(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(), R"(warning: e2 must be less than or equal to 128)");
 }
@@ -430,7 +430,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Log_F32_OutOfRange) {
     auto* a = constants.Get(f32(-1));
     auto result = eval.log(a->Type(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(), R"(warning: log must be called with a value > 0)");
 }
@@ -438,7 +438,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Log2_F32_OutOfRange) {
     auto* a = constants.Get(f32(-1));
     auto result = eval.log2(a->Type(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(), R"(warning: log2 must be called with a value > 0)");
 }
@@ -449,7 +449,7 @@
         eval.VecSplat(create<core::type::Vector>(create<core::type::F32>(), 4u), Vector{zero}, {})
             .Get();
     auto result = eval.normalize(vec->Type(), Vector{vec}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->Index(0)->ValueAs<f32>(), 0.f);
     EXPECT_EQ(result.Get()->Index(1)->ValueAs<f32>(), 0.f);
     EXPECT_EQ(result.Get()->Index(2)->ValueAs<f32>(), 0.f);
@@ -464,7 +464,7 @@
         eval.VecInitS(create<core::type::Vector>(create<core::type::F32>(), 2u), Vector{a, b}, {})
             .Get();
     auto result = eval.pack2x16float(create<core::type::U32>(), Vector{vec}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<u32>(), 0x51430000);
     EXPECT_EQ(error(), R"(warning: value 75250.0 cannot be represented as 'f16')");
 }
@@ -473,7 +473,7 @@
     auto* a = constants.Get(f32(2));
     auto* b = constants.Get(f32(1000));
     auto result = eval.pow(a->Type(), Vector{a, b}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(), R"(warning: '2.0 ^ 1000.0' cannot be represented as 'f32')");
 }
@@ -481,7 +481,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Unpack2x16Float_OutOfRange) {
     auto* a = constants.Get(u32(0x51437C00));
     auto result = eval.unpack2x16float(create<core::type::U32>(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_FLOAT_EQ(result.Get()->Index(0)->ValueAs<f32>(), 0.f);
     EXPECT_FLOAT_EQ(result.Get()->Index(1)->ValueAs<f32>(), 42.09375f);
     EXPECT_EQ(error(), R"(warning: value inf cannot be represented as 'f32')");
@@ -490,7 +490,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, QuantizeToF16_OutOfRange) {
     auto* a = constants.Get(f32(75250.f));
     auto result = eval.quantizeToF16(create<core::type::U32>(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<u32>(), 0);
     EXPECT_EQ(error(), R"(warning: value 75250.0 cannot be represented as 'f16')");
 }
@@ -498,7 +498,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Sqrt_F32_OutOfRange) {
     auto* a = constants.Get(f32(-1));
     auto result = eval.sqrt(a->Type(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(), R"(warning: sqrt must be called with a value >= 0)");
 }
@@ -508,7 +508,7 @@
     auto* low = constants.Get(f32(2));
     auto* high = constants.Get(f32(1));
     auto result = eval.clamp(e->Type(), Vector{e, low, high}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 1.f);
     EXPECT_EQ(error(), R"(warning: clamp called with 'low' (2.0) greater than 'high' (1.0))");
 }
@@ -516,7 +516,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Bitcast_Infinity) {
     auto* a = constants.Get(u32(0x7F800000));
     auto result = eval.Bitcast(create<core::type::F32>(), a, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(), R"(warning: value inf cannot be represented as 'f32')");
 }
@@ -524,7 +524,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Bitcast_NaN) {
     auto* a = constants.Get(u32(0x7FC00000));
     auto result = eval.Bitcast(create<core::type::F32>(), a, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), 0.f);
     EXPECT_EQ(error(), R"(warning: value nan cannot be represented as 'f32')");
 }
@@ -532,7 +532,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Convert_F32_TooHigh) {
     auto* a = constants.Get(AFloat::Highest());
     auto result = eval.Convert(create<core::type::F32>(), a, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), f32::kHighestValue);
     EXPECT_EQ(
         error(),
@@ -542,7 +542,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Convert_F32_TooLow) {
     auto* a = constants.Get(AFloat::Lowest());
     auto result = eval.Convert(create<core::type::F32>(), a, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), f32::kLowestValue);
     EXPECT_EQ(
         error(),
@@ -552,7 +552,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Convert_F16_TooHigh) {
     auto* a = constants.Get(f32(1000000.0));
     auto result = eval.Convert(create<core::type::F16>(), a, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), f16::kHighestValue);
     EXPECT_EQ(error(), R"(warning: value 1000000.0 cannot be represented as 'f16')");
 }
@@ -560,7 +560,7 @@
 TEST_F(ConstEvalRuntimeSemanticsTest, Convert_F16_TooLow) {
     auto* a = constants.Get(f32(-1000000.0));
     auto result = eval.Convert(create<core::type::F16>(), a, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->ValueAs<f32>(), f16::kLowestValue);
     EXPECT_EQ(error(), R"(warning: value -1000000.0 cannot be represented as 'f16')");
 }
@@ -578,7 +578,7 @@
                             {})
                   .Get();
     auto result = eval.sqrt(a->Type(), Vector{a}, {});
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     EXPECT_EQ(result.Get()->Index(0)->ValueAs<f32>(), 1);
     EXPECT_EQ(result.Get()->Index(1)->ValueAs<f32>(), 2);
     EXPECT_EQ(result.Get()->Index(2)->ValueAs<f32>(), 0);
diff --git a/src/tint/lang/core/intrinsic/table.cc b/src/tint/lang/core/intrinsic/table.cc
index 3677866..aa38b22 100644
--- a/src/tint/lang/core/intrinsic/table.cc
+++ b/src/tint/lang/core/intrinsic/table.cc
@@ -198,7 +198,7 @@
                 ss << ", ";
             }
             first = false;
-            ss << arg->UnwrapRef()->FriendlyName();
+            ss << arg->FriendlyName();
         }
     }
     ss << ")";
@@ -321,7 +321,7 @@
         auto* type_indices = context.data[parameter.type_matcher_indices];
         auto* number_indices = context.data[parameter.number_matcher_indices];
         if (!Match(context, templates, overload, type_indices, number_indices, earliest_eval_stage)
-                 .Type(args[p]->UnwrapRef())) {
+                 .Type(args[p])) {
             score += kMismatchedParamTypePenalty;
         }
     }
@@ -380,7 +380,7 @@
             auto* number_indices = context.data[parameter.number_matcher_indices];
             auto* ty = Match(context, templates, overload, type_indices, number_indices,
                              earliest_eval_stage)
-                           .Type(args[p]->UnwrapRef());
+                           .Type(args[p]);
             parameters.Emplace(ty, parameter.usage);
         }
     }
diff --git a/src/tint/lang/core/intrinsic/table_test.cc b/src/tint/lang/core/intrinsic/table_test.cc
index 87fbe57..d76c548 100644
--- a/src/tint/lang/core/intrinsic/table_test.cc
+++ b/src/tint/lang/core/intrinsic/table_test.cc
@@ -70,7 +70,7 @@
     auto* f32 = create<core::type::F32>();
     auto result =
         table.Lookup(core::BuiltinFn::kCos, Vector{f32}, EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -81,7 +81,7 @@
     auto* i32 = create<core::type::I32>();
     auto result =
         table.Lookup(core::BuiltinFn::kCos, Vector{i32}, EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -91,7 +91,7 @@
     auto* vec2_f32 = create<core::type::Vector>(f32, 2u);
     auto result = table.Lookup(core::BuiltinFn::kUnpack2X16Float, Vector{u32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec2_f32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -102,7 +102,7 @@
     auto* f32 = create<core::type::F32>();
     auto result = table.Lookup(core::BuiltinFn::kUnpack2X16Float, Vector{f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -113,7 +113,7 @@
     auto* tex = create<core::type::SampledTexture>(core::type::TextureDimension::k1d, f32);
     auto result = table.Lookup(core::BuiltinFn::kTextureLoad, Vector{tex, i32, i32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec4_f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -130,7 +130,7 @@
     auto* tex = create<core::type::SampledTexture>(core::type::TextureDimension::k1d, f32);
     auto result = table.Lookup(core::BuiltinFn::kTextureLoad, Vector{tex, f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -138,7 +138,7 @@
     auto* i32 = create<core::type::I32>();
     auto result = table.Lookup(core::BuiltinFn::kCountOneBits, Vector{i32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, i32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -149,7 +149,7 @@
     auto* u32 = create<core::type::U32>();
     auto result = table.Lookup(core::BuiltinFn::kCountOneBits, Vector{u32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, u32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -160,7 +160,7 @@
     auto* f32 = create<core::type::F32>();
     auto result = table.Lookup(core::BuiltinFn::kCountOneBits, Vector{f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -168,7 +168,7 @@
     auto* i32 = create<core::type::I32>();
     auto result = table.Lookup(core::BuiltinFn::kClamp, Vector{i32, i32, i32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, i32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -181,7 +181,7 @@
     auto* u32 = create<core::type::U32>();
     auto result = table.Lookup(core::BuiltinFn::kClamp, Vector{u32, u32, u32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, u32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -194,7 +194,7 @@
     auto* f32 = create<core::type::F32>();
     auto result = table.Lookup(core::BuiltinFn::kClamp, Vector{f32, f32, f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -207,7 +207,7 @@
     auto* bool_ = create<core::type::Bool>();
     auto result = table.Lookup(core::BuiltinFn::kClamp, Vector{bool_, bool_, bool_},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -216,7 +216,7 @@
     auto* bool_ = create<core::type::Bool>();
     auto result = table.Lookup(core::BuiltinFn::kSelect, Vector{f32, f32, bool_},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -229,7 +229,7 @@
     auto* f32 = create<core::type::F32>();
     auto result = table.Lookup(core::BuiltinFn::kSelect, Vector{f32, f32, f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -240,7 +240,7 @@
                                             core::Access::kReadWrite);
     auto result = table.Lookup(core::BuiltinFn::kAtomicLoad, Vector{ptr},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, i32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -252,7 +252,7 @@
     auto* atomicI32 = create<core::type::Atomic>(i32);
     auto result = table.Lookup(core::BuiltinFn::kAtomicLoad, Vector{atomicI32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -263,7 +263,7 @@
         create<core::type::Pointer>(core::AddressSpace::kStorage, arr, core::Access::kReadWrite);
     auto result = table.Lookup(core::BuiltinFn::kArrayLength, Vector{arr_ptr},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_TRUE(result->return_type->Is<core::type::U32>());
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -276,7 +276,7 @@
     auto* f32 = create<core::type::F32>();
     auto result = table.Lookup(core::BuiltinFn::kArrayLength, Vector{f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -288,7 +288,7 @@
     auto* sampler = create<core::type::Sampler>(core::type::SamplerKind::kSampler);
     auto result = table.Lookup(core::BuiltinFn::kTextureSample, Vector{tex, sampler, vec2_f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec4_f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -306,7 +306,7 @@
     auto* tex = create<core::type::SampledTexture>(core::type::TextureDimension::k2d, f32);
     auto result = table.Lookup(core::BuiltinFn::kTextureSample, Vector{tex, f32, vec2_f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -318,7 +318,7 @@
     auto* tex = create<core::type::SampledTexture>(core::type::TextureDimension::k2d, f32);
     auto result = table.Lookup(core::BuiltinFn::kTextureLoad, Vector{tex, vec2_i32, i32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec4_f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -338,7 +338,7 @@
     auto* tex = create<core::type::MultisampledTexture>(core::type::TextureDimension::k2d, f32);
     auto result = table.Lookup(core::BuiltinFn::kTextureLoad, Vector{tex, vec2_i32, i32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec4_f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -357,7 +357,7 @@
     auto* tex = create<core::type::DepthTexture>(core::type::TextureDimension::k2d);
     auto result = table.Lookup(core::BuiltinFn::kTextureLoad, Vector{tex, vec2_i32, i32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -376,7 +376,7 @@
     auto* tex = create<core::type::DepthMultisampledTexture>(core::type::TextureDimension::k2d);
     auto result = table.Lookup(core::BuiltinFn::kTextureLoad, Vector{tex, vec2_i32, i32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -396,7 +396,7 @@
     auto* tex = create<core::type::ExternalTexture>();
     auto result = table.Lookup(core::BuiltinFn::kTextureLoad, Vector{tex, vec2_i32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec4_f32);
     ASSERT_EQ(result->parameters.Length(), 2u);
@@ -418,7 +418,7 @@
 
     auto result = table.Lookup(core::BuiltinFn::kTextureStore, Vector{tex, vec2_i32, vec4_f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_TRUE(result->return_type->Is<type::Void>());
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -436,30 +436,15 @@
     auto* vec2_i32 = create<core::type::Vector>(i32, 2u);
     auto result = table.Lookup(core::BuiltinFn::kTextureLoad, Vector{f32, vec2_i32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
-TEST_F(IntrinsicTableTest, ImplicitLoadOnReference) {
-    auto* f32 = create<core::type::F32>();
-    auto result = table.Lookup(core::BuiltinFn::kCos,
-                               Vector{
-                                   create<core::type::Reference>(core::AddressSpace::kFunction, f32,
-                                                                 core::Access::kReadWrite),
-                               },
-                               EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
-    ASSERT_EQ(Diagnostics().str(), "");
-    EXPECT_EQ(result->return_type, f32);
-    ASSERT_EQ(result->parameters.Length(), 1u);
-    EXPECT_EQ(result->parameters[0].type, f32);
-}
-
 TEST_F(IntrinsicTableTest, MatchTemplateType) {
     auto* f32 = create<core::type::F32>();
     auto result = table.Lookup(core::BuiltinFn::kClamp, Vector{f32, f32, f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     EXPECT_EQ(result->parameters[0].type, f32);
@@ -472,7 +457,7 @@
     auto* u32 = create<core::type::U32>();
     auto result = table.Lookup(core::BuiltinFn::kClamp, Vector{f32, u32, f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -481,7 +466,7 @@
     auto* vec2_f32 = create<core::type::Vector>(f32, 2u);
     auto result = table.Lookup(core::BuiltinFn::kClamp, Vector{vec2_f32, vec2_f32, vec2_f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, vec2_f32);
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -496,7 +481,7 @@
     auto* vec2_f32 = create<core::type::Vector>(f32, 2u);
     auto result = table.Lookup(core::BuiltinFn::kClamp, Vector{vec2_f32, u32, vec2_f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -506,7 +491,7 @@
     auto* mat3_f32 = create<core::type::Matrix>(vec3_f32, 3u);
     auto result = table.Lookup(core::BuiltinFn::kDeterminant, Vector{mat3_f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, f32);
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -519,7 +504,7 @@
     auto* mat3x2_f32 = create<core::type::Matrix>(vec2_f32, 3u);
     auto result = table.Lookup(core::BuiltinFn::kDeterminant, Vector{mat3x2_f32},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -528,7 +513,7 @@
     auto* bool_ = create<core::type::Bool>();
     auto result = table.Lookup(core::BuiltinFn::kSelect, Vector{af, af, bool_},
                                EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_NE(result->const_eval_fn, nullptr);
     EXPECT_EQ(result->return_type, af);
@@ -540,11 +525,9 @@
 
 TEST_F(IntrinsicTableTest, MatchDifferentArgsElementType_Builtin_RuntimeEval) {
     auto* af = create<core::type::AbstractFloat>();
-    auto* bool_ref = create<core::type::Reference>(
-        core::AddressSpace::kFunction, create<core::type::Bool>(), core::Access::kReadWrite);
-    auto result = table.Lookup(core::BuiltinFn::kSelect, Vector{af, af, bool_ref},
+    auto result = table.Lookup(core::BuiltinFn::kSelect, Vector{af, af, create<core::type::Bool>()},
                                EvaluationStage::kRuntime, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_NE(result->const_eval_fn, nullptr);
     EXPECT_TRUE(result->return_type->Is<core::type::F32>());
@@ -559,7 +542,7 @@
     auto* u32 = create<core::type::U32>();
     auto result = table.Lookup(core::BinaryOp::kShiftLeft, ai, u32, EvaluationStage::kConstant,
                                Source{}, false);
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_NE(result->const_eval_fn, nullptr) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_EQ(result->return_type, ai);
@@ -572,7 +555,7 @@
     auto* u32 = create<core::type::U32>();
     auto result = table.Lookup(core::BinaryOp::kShiftLeft, ai, u32, EvaluationStage::kRuntime,
                                Source{}, false);
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     ASSERT_NE(result->const_eval_fn, nullptr) << Diagnostics();
     ASSERT_EQ(Diagnostics().str(), "");
     EXPECT_TRUE(result->return_type->Is<core::type::I32>());
@@ -586,7 +569,7 @@
     auto* bool_ = create<core::type::Bool>();
     auto result = table.Lookup(core::BuiltinFn::kTextureDimensions, Vector{bool_, bool_},
                                EvaluationStage::kConstant, Source{});
-    EXPECT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_EQ(Diagnostics().str(),
               R"(error: no matching call to textureDimensions(bool, bool)
 
@@ -626,7 +609,7 @@
     auto* bool_ = create<core::type::Bool>();
     auto result = table.Lookup(core::BuiltinFn::kTextureDimensions, Vector{tex, bool_},
                                EvaluationStage::kConstant, Source{});
-    EXPECT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_EQ(Diagnostics().str(),
               R"(error: no matching call to textureDimensions(texture_depth_2d, bool)
 
@@ -674,7 +657,7 @@
     auto* bool_ = create<core::type::Bool>();
     auto result =
         table.Lookup(core::UnaryOp::kNegation, bool_, EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     EXPECT_EQ(Diagnostics().str(), R"(12:34 error: no matching overload for operator - (bool)
 
 2 candidate operators:
@@ -718,7 +701,7 @@
     auto result = table.Lookup(core::BinaryOp::kMultiply, f32, bool_, EvaluationStage::kConstant,
                                Source{{12, 34}},
                                /* is_compound */ false);
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     EXPECT_EQ(Diagnostics().str(), R"(12:34 error: no matching overload for operator * (f32, bool)
 
 9 candidate operators:
@@ -752,7 +735,7 @@
     auto result = table.Lookup(core::BinaryOp::kMultiply, f32, bool_, EvaluationStage::kConstant,
                                Source{{12, 34}},
                                /* is_compound */ true);
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     EXPECT_EQ(Diagnostics().str(), R"(12:34 error: no matching overload for operator *= (f32, bool)
 
 9 candidate operators:
@@ -773,7 +756,7 @@
     auto* vec3_i32 = create<core::type::Vector>(i32, 3u);
     auto result = table.Lookup(CtorConv::kVec3, nullptr, Vector{i32, i32, i32},
                                EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     EXPECT_EQ(result->return_type, vec3_i32);
     EXPECT_TRUE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -788,7 +771,7 @@
     auto* vec3_i32 = create<core::type::Vector>(i32, 3u);
     auto result = table.Lookup(CtorConv::kVec3, i32, Vector{i32, i32, i32},
                                EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     EXPECT_EQ(result->return_type, vec3_i32);
     EXPECT_TRUE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
     ASSERT_EQ(result->parameters.Length(), 3u);
@@ -803,7 +786,7 @@
     auto* f32 = create<core::type::F32>();
     auto result = table.Lookup(CtorConv::kVec3, nullptr, Vector{i32, f32, i32},
                                EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     EXPECT_EQ(Diagnostics().str(),
               R"(12:34 error: no matching constructor for vec3(i32, f32, i32)
 
@@ -830,7 +813,7 @@
     auto* f32 = create<core::type::F32>();
     auto result = table.Lookup(CtorConv::kVec3, i32, Vector{i32, f32, i32},
                                EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     EXPECT_EQ(Diagnostics().str(),
               R"(12:34 error: no matching constructor for vec3<i32>(i32, f32, i32)
 
@@ -857,7 +840,7 @@
     auto* vec3_ai = create<core::type::Vector>(ai, 3u);
     auto result = table.Lookup(CtorConv::kVec3, nullptr, Vector{vec3_ai},
                                EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     EXPECT_EQ(result->return_type, vec3_ai);
     EXPECT_TRUE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -872,7 +855,7 @@
     auto* mat2x2_af = create<core::type::Matrix>(vec2_af, 2u);
     auto result = table.Lookup(CtorConv::kMat2x2, nullptr, Vector{vec2_ai, vec2_ai},
                                EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     EXPECT_TYPE(result->return_type, mat2x2_af);
     EXPECT_TRUE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
     ASSERT_EQ(result->parameters.Length(), 2u);
@@ -886,7 +869,7 @@
     auto* vec3_ai = create<core::type::Vector>(ai, 3u);
     auto result = table.Lookup(CtorConv::kVec3, nullptr, Vector{ai, ai, ai},
                                EvaluationStage::kConstant, Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     EXPECT_NE(result->const_eval_fn, nullptr);
     EXPECT_EQ(result->return_type, vec3_ai);
     EXPECT_TRUE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
@@ -903,7 +886,7 @@
                                EvaluationStage::kRuntime, Source{{12, 34}});
     auto* i32 = create<type::I32>();
     auto* vec3_i32 = create<type::Vector>(i32, 3u);
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     EXPECT_NE(result->const_eval_fn, nullptr);
     EXPECT_EQ(result->return_type, vec3_i32);
     EXPECT_TRUE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
@@ -921,7 +904,7 @@
     auto* vec3_f32 = create<core::type::Vector>(f32, 3u);
     auto result = table.Lookup(CtorConv::kVec3, i32, Vector{vec3_f32}, EvaluationStage::kConstant,
                                Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     EXPECT_EQ(result->return_type, vec3_i32);
     EXPECT_FALSE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
     ASSERT_EQ(result->parameters.Length(), 1u);
@@ -934,7 +917,7 @@
     auto* f32 = create<core::type::F32>();
     auto result = table.Lookup(CtorConv::kVec3, f32, Vector{arr}, EvaluationStage::kConstant,
                                Source{{12, 34}});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     EXPECT_EQ(Diagnostics().str(),
               R"(12:34 error: no matching constructor for vec3<f32>(array<u32>)
 
@@ -964,7 +947,7 @@
     auto* vec3_f32 = create<core::type::Vector>(f32, 3u);
     auto result = table.Lookup(CtorConv::kVec3, af, Vector{vec3_ai}, EvaluationStage::kConstant,
                                Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     EXPECT_NE(result->const_eval_fn, nullptr);
     // NOTE: Conversions are explicit, so there's no way to have it return abstracts
     EXPECT_EQ(result->return_type, vec3_f32);
@@ -981,7 +964,7 @@
     auto* vec3_i32 = create<core::type::Vector>(create<core::type::I32>(), 3u);
     auto result = table.Lookup(CtorConv::kVec3, af, Vector{vec3_ai}, EvaluationStage::kRuntime,
                                Source{{12, 34}});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     EXPECT_NE(result->const_eval_fn, nullptr);
     EXPECT_EQ(result->return_type, vec3_f32);
     EXPECT_FALSE(result->info->flags.Contains(OverloadFlag::kIsConstructor));
@@ -995,7 +978,7 @@
     arg_tys.Resize(257, f32);
     auto result = table.Lookup(core::BuiltinFn::kAbs, std::move(arg_tys),
                                EvaluationStage::kConstant, Source{});
-    ASSERT_FALSE(result);
+    ASSERT_NE(result, Success);
     ASSERT_THAT(Diagnostics().str(), HasSubstr("no matching call"));
 }
 
@@ -1008,7 +991,7 @@
     auto* i32 = create<core::type::I32>();
     auto result =
         table.Lookup(CtorConv::kI32, nullptr, Vector{ai}, EvaluationStage::kConstant, Source{});
-    ASSERT_TRUE(result) << Diagnostics();
+    ASSERT_EQ(result, Success) << Diagnostics();
     EXPECT_EQ(result->return_type, i32);
     EXPECT_EQ(result->parameters.Length(), 1u);
     EXPECT_EQ(result->parameters[0].type, ai);
@@ -1053,7 +1036,7 @@
                                Source{{12, 34}},
                                /* is_compound */ false);
 
-    bool matched = result;
+    bool matched = result == Success;
     bool expected_match = GetParam().expected_match;
     EXPECT_EQ(matched, expected_match) << Diagnostics();
 
@@ -1238,21 +1221,21 @@
                                 EvaluationStage::kConstant, Source{{12, 34}});
 
     bool expected_match = GetParam().expected_match;
-    EXPECT_EQ(builtin == true, expected_match) << Diagnostics();
+    EXPECT_EQ(builtin == Success, expected_match) << Diagnostics();
 
-    auto* result = builtin ? builtin->return_type : nullptr;
+    auto* result = builtin == Success ? builtin->return_type : nullptr;
     auto* expected_result = GetParam().expected_result(*this);
     EXPECT_TYPE(result, expected_result);
 
-    auto* param_a = builtin ? builtin->parameters[0].type : nullptr;
+    auto* param_a = builtin == Success ? builtin->parameters[0].type : nullptr;
     auto* expected_param_a = GetParam().expected_param_a(*this);
     EXPECT_TYPE(param_a, expected_param_a);
 
-    auto* param_b = builtin ? builtin->parameters[1].type : nullptr;
+    auto* param_b = builtin == Success ? builtin->parameters[1].type : nullptr;
     auto* expected_param_b = GetParam().expected_param_b(*this);
     EXPECT_TYPE(param_b, expected_param_b);
 
-    auto* param_c = builtin ? builtin->parameters[2].type : nullptr;
+    auto* param_c = builtin == Success ? builtin->parameters[2].type : nullptr;
     auto* expected_param_c = GetParam().expected_param_c(*this);
     EXPECT_TYPE(param_c, expected_param_c);
 }
diff --git a/src/tint/lang/core/ir/binary/decode.cc b/src/tint/lang/core/ir/binary/decode.cc
index 7141a28..fb77a96 100644
--- a/src/tint/lang/core/ir/binary/decode.cc
+++ b/src/tint/lang/core/ir/binary/decode.cc
@@ -31,7 +31,10 @@
 
 #include "src/tint/lang/core/ir/builder.h"
 #include "src/tint/lang/core/ir/module.h"
+#include "src/tint/lang/core/type/depth_multisampled_texture.h"
 #include "src/tint/lang/core/type/depth_texture.h"
+#include "src/tint/lang/core/type/external_texture.h"
+#include "src/tint/lang/core/type/multisampled_texture.h"
 #include "src/tint/lang/core/type/sampled_texture.h"
 #include "src/tint/lang/core/type/storage_texture.h"
 #include "src/tint/utils/containers/transform.h"
@@ -171,13 +174,13 @@
             params_out.Push(ValueAs<ir::FunctionParam>(param_in));
         }
         if (fn_in.has_return_location()) {
-            auto& ret_loc_in = fn_in.return_location();
-            core::ir::Location ret_loc_out{};
-            ret_loc_out.value = ret_loc_in.value();
-            if (ret_loc_in.has_interpolation()) {
-                ret_loc_out.interpolation = Interpolation(ret_loc_in.interpolation());
-            }
-            fn_out->SetReturnLocation(ret_loc_out.value, std::move(ret_loc_out.interpolation));
+            fn_out->SetReturnLocation(Location(fn_in.return_location()));
+        }
+        if (fn_in.has_return_builtin()) {
+            fn_out->SetReturnBuiltin(BuiltinValue(fn_in.return_builtin()));
+        }
+        if (fn_in.return_invariant()) {
+            fn_out->SetReturnInvariant(true);
         }
         fn_out->SetParams(std::move(params_out));
         fn_out->SetBlock(Block(fn_in.block()));
@@ -244,6 +247,9 @@
             case pb::Instruction::KindCase::kBinary:
                 inst_out = CreateInstructionBinary(inst_in.binary());
                 break;
+            case pb::Instruction::KindCase::kBitcast:
+                inst_out = CreateInstructionBitcast(inst_in.bitcast());
+                break;
             case pb::Instruction::KindCase::kBreakIf:
                 inst_out = CreateInstructionBreakIf(inst_in.break_if());
                 break;
@@ -313,6 +319,9 @@
             case pb::Instruction::KindCase::kVar:
                 inst_out = CreateInstructionVar(inst_in.var());
                 break;
+            case pb::Instruction::KindCase::kUnreachable:
+                inst_out = CreateInstructionUnreachable(inst_in.unreachable());
+                break;
             default:
                 TINT_UNIMPLEMENTED() << inst_in.kind_case();
                 break;
@@ -344,6 +353,10 @@
         return binary_out;
     }
 
+    ir::Bitcast* CreateInstructionBitcast(const pb::InstructionBitcast&) {
+        return mod_out_.instructions.Create<ir::Bitcast>();
+    }
+
     ir::BreakIf* CreateInstructionBreakIf(const pb::InstructionBreakIf&) {
         auto* break_if_out = mod_out_.instructions.Create<ir::BreakIf>();
         break_ifs_.Push(break_if_out);
@@ -500,6 +513,10 @@
         return var_out;
     }
 
+    ir::Unreachable* CreateInstructionUnreachable(const pb::InstructionUnreachable&) {
+        return b.Unreachable();
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Types
     ////////////////////////////////////////////////////////////////////////////
@@ -523,8 +540,14 @@
                 return CreateTypeDepthTexture(type_in.depth_texture());
             case pb::Type::KindCase::kSampledTexture:
                 return CreateTypeSampledTexture(type_in.sampled_texture());
+            case pb::Type::KindCase::kMultisampledTexture:
+                return CreateTypeMultisampledTexture(type_in.multisampled_texture());
+            case pb::Type::KindCase::kDepthMultisampledTexture:
+                return CreateTypeDepthMultisampledTexture(type_in.depth_multisampled_texture());
             case pb::Type::KindCase::kStorageTexture:
                 return CreateTypeStorageTexture(type_in.storage_texture());
+            case pb::Type::KindCase::kExternalTexture:
+                return CreateTypeExternalTexture(type_in.external_texture());
             case pb::Type::KindCase::kSampler:
                 return CreateTypeSampler(type_in.sampler());
             default:
@@ -635,6 +658,19 @@
         return mod_out_.Types().Get<type::SampledTexture>(dimension, sub_type);
     }
 
+    const type::MultisampledTexture* CreateTypeMultisampledTexture(
+        const pb::TypeMultisampledTexture& texture_in) {
+        auto dimension = TextureDimension(texture_in.dimension());
+        auto sub_type = Type(texture_in.sub_type());
+        return mod_out_.Types().Get<type::MultisampledTexture>(dimension, sub_type);
+    }
+
+    const type::DepthMultisampledTexture* CreateTypeDepthMultisampledTexture(
+        const pb::TypeDepthMultisampledTexture& texture_in) {
+        auto dimension = TextureDimension(texture_in.dimension());
+        return mod_out_.Types().Get<type::DepthMultisampledTexture>(dimension);
+    }
+
     const type::StorageTexture* CreateTypeStorageTexture(const pb::TypeStorageTexture& texture_in) {
         auto dimension = TextureDimension(texture_in.dimension());
         auto texel_format = TexelFormat(texture_in.texel_format());
@@ -644,6 +680,10 @@
             type::StorageTexture::SubtypeFor(texel_format, b.ir.Types()));
     }
 
+    const type::ExternalTexture* CreateTypeExternalTexture(const pb::TypeExternalTexture&) {
+        return mod_out_.Types().Get<type::ExternalTexture>();
+    }
+
     const type::Sampler* CreateTypeSampler(const pb::TypeSampler& sampler_in) {
         auto kind = SamplerKind(sampler_in.kind());
         return mod_out_.Types().Get<type::Sampler>(kind);
@@ -657,41 +697,21 @@
     ir::Value* CreateValue(const pb::Value& value_in) {
         ir::Value* value_out = nullptr;
         switch (value_in.kind_case()) {
-            case pb::Value::KindCase::kFunction: {
+            case pb::Value::KindCase::kFunction:
                 value_out = Function(value_in.function());
                 break;
-            }
-            case pb::Value::KindCase::kInstructionResult: {
-                auto& res_in = value_in.instruction_result();
-                auto* type = Type(res_in.type());
-                value_out = b.InstructionResult(type);
-                if (res_in.has_name()) {
-                    mod_out_.SetName(value_out, res_in.name());
-                }
+            case pb::Value::KindCase::kInstructionResult:
+                value_out = InstructionResult(value_in.instruction_result());
                 break;
-            }
-            case pb::Value::KindCase::kFunctionParameter: {
-                auto& param_in = value_in.function_parameter();
-                auto* type = Type(param_in.type());
-                value_out = b.FunctionParam(type);
-                if (param_in.has_name()) {
-                    mod_out_.SetName(value_out, param_in.name());
-                }
+            case pb::Value::KindCase::kFunctionParameter:
+                value_out = FunctionParameter(value_in.function_parameter());
                 break;
-            }
-            case pb::Value::KindCase::kBlockParameter: {
-                auto& param_in = value_in.block_parameter();
-                auto* type = Type(param_in.type());
-                value_out = b.BlockParam(type);
-                if (param_in.has_name()) {
-                    mod_out_.SetName(value_out, param_in.name());
-                }
+            case pb::Value::KindCase::kBlockParameter:
+                value_out = BlockParameter(value_in.block_parameter());
                 break;
-            }
-            case pb::Value::KindCase::kConstant: {
+            case pb::Value::KindCase::kConstant:
                 value_out = b.Constant(ConstantValue(value_in.constant()));
                 break;
-            }
             default:
                 TINT_ICE() << "invalid TypeDecl.kind: " << value_in.kind_case();
                 return nullptr;
@@ -699,6 +719,51 @@
         return value_out;
     }
 
+    ir::InstructionResult* InstructionResult(const pb::InstructionResult& res_in) {
+        auto* type = Type(res_in.type());
+        auto* res_out = b.InstructionResult(type);
+        if (res_in.has_name()) {
+            mod_out_.SetName(res_out, res_in.name());
+        }
+        return res_out;
+    }
+
+    ir::FunctionParam* FunctionParameter(const pb::FunctionParameter& param_in) {
+        auto* type = Type(param_in.type());
+        auto* param_out = b.FunctionParam(type);
+        if (param_in.has_name()) {
+            mod_out_.SetName(param_out, param_in.name());
+        }
+
+        if (param_in.has_attributes()) {
+            auto& attrs_in = param_in.attributes();
+            if (attrs_in.has_binding_point()) {
+                auto& bp_in = attrs_in.binding_point();
+                param_out->SetBindingPoint(bp_in.group(), bp_in.binding());
+            }
+            if (attrs_in.has_location()) {
+                param_out->SetLocation(Location(attrs_in.location()));
+            }
+            if (attrs_in.has_builtin()) {
+                param_out->SetBuiltin(BuiltinValue(attrs_in.builtin()));
+            }
+            if (attrs_in.invariant()) {
+                param_out->SetInvariant(true);
+            }
+        }
+
+        return param_out;
+    }
+
+    ir::BlockParam* BlockParameter(const pb::BlockParameter& param_in) {
+        auto* type = Type(param_in.type());
+        auto* param_out = b.BlockParam(type);
+        if (param_in.has_name()) {
+            mod_out_.SetName(param_out, param_in.name());
+        }
+        return param_out;
+    }
+
     ir::Value* Value(uint32_t id) { return id > 0 ? values_[id - 1] : nullptr; }
 
     template <typename T>
@@ -770,6 +835,15 @@
     ////////////////////////////////////////////////////////////////////////////
     // Attributes
     ////////////////////////////////////////////////////////////////////////////
+    ir::Location Location(const pb::Location& location_in) {
+        core::ir::Location location_out{};
+        location_out.value = location_in.value();
+        if (location_in.has_interpolation()) {
+            location_out.interpolation = Interpolation(location_in.interpolation());
+        }
+        return location_out;
+    }
+
     core::Interpolation Interpolation(const pb::Interpolation& interpolation_in) {
         core::Interpolation interpolation_out{};
         interpolation_out.type = InterpolationType(interpolation_in.type());
diff --git a/src/tint/lang/core/ir/binary/encode.cc b/src/tint/lang/core/ir/binary/encode.cc
index 3677967..fa72a55 100644
--- a/src/tint/lang/core/ir/binary/encode.cc
+++ b/src/tint/lang/core/ir/binary/encode.cc
@@ -36,6 +36,7 @@
 #include "src/tint/lang/core/constant/splat.h"
 #include "src/tint/lang/core/ir/access.h"
 #include "src/tint/lang/core/ir/binary.h"
+#include "src/tint/lang/core/ir/bitcast.h"
 #include "src/tint/lang/core/ir/break_if.h"
 #include "src/tint/lang/core/ir/construct.h"
 #include "src/tint/lang/core/ir/continue.h"
@@ -60,16 +61,20 @@
 #include "src/tint/lang/core/ir/switch.h"
 #include "src/tint/lang/core/ir/swizzle.h"
 #include "src/tint/lang/core/ir/unary.h"
+#include "src/tint/lang/core/ir/unreachable.h"
 #include "src/tint/lang/core/ir/user_call.h"
 #include "src/tint/lang/core/ir/var.h"
 #include "src/tint/lang/core/texel_format.h"
 #include "src/tint/lang/core/type/array.h"
 #include "src/tint/lang/core/type/bool.h"
+#include "src/tint/lang/core/type/depth_multisampled_texture.h"
 #include "src/tint/lang/core/type/depth_texture.h"
+#include "src/tint/lang/core/type/external_texture.h"
 #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/type/sampler.h"
@@ -136,11 +141,13 @@
         }
         if (auto ret_loc_in = fn_in->ReturnLocation()) {
             auto& ret_loc_out = *fn_out->mutable_return_location();
-            if (auto interpolation_in = ret_loc_in->interpolation) {
-                auto& interpolation_out = *ret_loc_out.mutable_interpolation();
-                Interpolation(interpolation_out, *interpolation_in);
-            }
-            ret_loc_out.set_value(ret_loc_in->value);
+            Location(ret_loc_out, *ret_loc_in);
+        }
+        if (auto builtin_in = fn_in->ReturnBuiltin()) {
+            fn_out->set_return_builtin(BuiltinValue(*builtin_in));
+        }
+        if (fn_in->ReturnInvariant()) {
+            fn_out->set_return_invariant(true);
         }
         fn_out->set_block(Block(fn_in->Block()));
     }
@@ -192,6 +199,7 @@
             inst_in,  //
             [&](const ir::Access* i) { InstructionAccess(*inst_out.mutable_access(), i); },
             [&](const ir::Binary* i) { InstructionBinary(*inst_out.mutable_binary(), i); },
+            [&](const ir::Bitcast* i) { InstructionBitcast(*inst_out.mutable_bitcast(), i); },
             [&](const ir::BreakIf* i) { InstructionBreakIf(*inst_out.mutable_break_if(), i); },
             [&](const ir::CoreBuiltinCall* i) {
                 InstructionBuiltinCall(*inst_out.mutable_builtin_call(), i);
@@ -225,6 +233,9 @@
             [&](const ir::Unary* i) { InstructionUnary(*inst_out.mutable_unary(), i); },
             [&](const ir::UserCall* i) { InstructionUserCall(*inst_out.mutable_user_call(), i); },
             [&](const ir::Var* i) { InstructionVar(*inst_out.mutable_var(), i); },
+            [&](const ir::Unreachable* i) {
+                InstructionUnreachable(*inst_out.mutable_unreachable(), i);
+            },
             TINT_ICE_ON_NO_MATCH);
         for (auto* operand : inst_in->Operands()) {
             inst_out.add_operands(Value(operand));
@@ -240,6 +251,8 @@
         binary_out.set_op(BinaryOp(binary_in->Op()));
     }
 
+    void InstructionBitcast(pb::InstructionBitcast&, const ir::Bitcast*) {}
+
     void InstructionBreakIf(pb::InstructionBreakIf&, const ir::BreakIf*) {}
 
     void InstructionBuiltinCall(pb::InstructionBuiltinCall& call_out,
@@ -325,11 +338,12 @@
     void InstructionVar(pb::InstructionVar& var_out, const ir::Var* var_in) {
         if (auto bp_in = var_in->BindingPoint()) {
             auto& bp_out = *var_out.mutable_binding_point();
-            bp_out.set_group(bp_in->group);
-            bp_out.set_binding(bp_in->binding);
+            BindingPoint(bp_out, *bp_in);
         }
     }
 
+    void InstructionUnreachable(pb::InstructionUnreachable&, const ir::Unreachable*) {}
+
     ////////////////////////////////////////////////////////////////////////////
     // Types
     ////////////////////////////////////////////////////////////////////////////
@@ -359,9 +373,18 @@
                 [&](const core::type::SampledTexture* t) {
                     TypeSampledTexture(*type_out.mutable_sampled_texture(), t);
                 },
+                [&](const core::type::MultisampledTexture* t) {
+                    TypeMultisampledTexture(*type_out.mutable_multisampled_texture(), t);
+                },
+                [&](const core::type::DepthMultisampledTexture* t) {
+                    TypeDepthMultisampledTexture(*type_out.mutable_depth_multisampled_texture(), t);
+                },
                 [&](const core::type::StorageTexture* t) {
                     TypeStorageTexture(*type_out.mutable_storage_texture(), t);
                 },
+                [&](const core::type::ExternalTexture* t) {
+                    TypeExternalTexture(*type_out.mutable_external_texture(), t);
+                },
                 [&](const core::type::Sampler* s) { TypeSampler(*type_out.mutable_sampler(), s); },
                 TINT_ICE_ON_NO_MATCH);
 
@@ -444,6 +467,17 @@
         texture_out.set_sub_type(Type(texture_in->type()));
     }
 
+    void TypeMultisampledTexture(pb::TypeMultisampledTexture& texture_out,
+                                 const core::type::MultisampledTexture* texture_in) {
+        texture_out.set_dimension(TextureDimension(texture_in->dim()));
+        texture_out.set_sub_type(Type(texture_in->type()));
+    }
+
+    void TypeDepthMultisampledTexture(pb::TypeDepthMultisampledTexture& texture_out,
+                                      const core::type::DepthMultisampledTexture* texture_in) {
+        texture_out.set_dimension(TextureDimension(texture_in->dim()));
+    }
+
     void TypeStorageTexture(pb::TypeStorageTexture& texture_out,
                             const core::type::StorageTexture* texture_in) {
         texture_out.set_dimension(TextureDimension(texture_in->dim()));
@@ -451,6 +485,8 @@
         texture_out.set_access(AccessControl(texture_in->access()));
     }
 
+    void TypeExternalTexture(pb::TypeExternalTexture&, const core::type::ExternalTexture*) {}
+
     void TypeSampler(pb::TypeSampler& sampler_out, const core::type::Sampler* sampler_in) {
         sampler_out.set_kind(SamplerKind(sampler_in->kind()));
     }
@@ -497,6 +533,20 @@
         if (auto name = mod_in_.NameOf(param_in); name.IsValid()) {
             param_out.set_name(name.Name());
         }
+        if (auto bp_in = param_in->BindingPoint()) {
+            auto& bp_out = *param_out.mutable_attributes()->mutable_binding_point();
+            BindingPoint(bp_out, *bp_in);
+        }
+        if (auto location_in = param_in->Location()) {
+            auto& location_out = *param_out.mutable_attributes()->mutable_location();
+            Location(location_out, *location_in);
+        }
+        if (auto builtin_in = param_in->Builtin()) {
+            param_out.mutable_attributes()->set_builtin(BuiltinValue(*builtin_in));
+        }
+        if (param_in->Invariant()) {
+            param_out.mutable_attributes()->set_invariant(true);
+        }
     }
 
     void BlockParameter(pb::BlockParameter& param_out, const ir::BlockParam* param_in) {
@@ -563,6 +613,14 @@
     ////////////////////////////////////////////////////////////////////////////
     // Attributes
     ////////////////////////////////////////////////////////////////////////////
+    void Location(pb::Location& location_out, const ir::Location& location_in) {
+        if (auto interpolation_in = location_in.interpolation) {
+            auto& interpolation_out = *location_out.mutable_interpolation();
+            Interpolation(interpolation_out, *interpolation_in);
+        }
+        location_out.set_value(location_in.value);
+    }
+
     void Interpolation(pb::Interpolation& interpolation_out,
                        const core::Interpolation& interpolation_in) {
         interpolation_out.set_type(InterpolationType(interpolation_in.type));
@@ -571,6 +629,11 @@
         }
     }
 
+    void BindingPoint(pb::BindingPoint& binding_point_out, const BindingPoint& binding_point_in) {
+        binding_point_out.set_group(binding_point_in.group);
+        binding_point_out.set_binding(binding_point_in.binding);
+    }
+
     ////////////////////////////////////////////////////////////////////////////
     // Enums
     ////////////////////////////////////////////////////////////////////////////
diff --git a/src/tint/lang/core/ir/binary/ir.proto b/src/tint/lang/core/ir/binary/ir.proto
index 6fe9364..8eb5c20 100644
--- a/src/tint/lang/core/ir/binary/ir.proto
+++ b/src/tint/lang/core/ir/binary/ir.proto
@@ -52,8 +52,11 @@
         TypeAtomic atomic = 7;
         TypeDepthTexture depth_texture = 8;
         TypeSampledTexture sampled_texture = 9;
-        TypeStorageTexture storage_texture = 10;
-        TypeSampler sampler = 11;
+        TypeMultisampledTexture multisampled_texture = 10;
+        TypeDepthMultisampledTexture depth_multisampled_texture = 11;
+        TypeStorageTexture storage_texture = 12;
+        TypeExternalTexture external_texture = 13;
+        TypeSampler sampler = 14;
     }
 }
 
@@ -116,12 +119,23 @@
     uint32 sub_type = 2;  // Module.types
 }
 
+message TypeMultisampledTexture {
+    TextureDimension dimension = 1;
+    uint32 sub_type = 2;  // Module.types
+}
+
+message TypeDepthMultisampledTexture {
+    TextureDimension dimension = 1;
+}
+
 message TypeStorageTexture {
     TextureDimension dimension = 1;
     TexelFormat texel_format = 2;
     AccessControl access = 3;
 }
 
+message TypeExternalTexture {}
+
 message TypeSampler {
     SamplerKind kind = 1;
 }
@@ -147,6 +161,7 @@
 message FunctionParameter {
     uint32 type = 1;  // Module.types
     optional string name = 2;
+    optional AttributesFunctionParameter attributes = 3;
 }
 
 message BlockParameter {
@@ -196,7 +211,9 @@
     optional PipelineStage pipeline_stage = 4;
     optional WorkgroupSize workgroup_size = 5;
     repeated uint32 parameters = 6;  // Module.values
-    optional ReturnLocation return_location = 7;
+    optional Location return_location = 7;
+    optional BuiltinValue return_builtin = 8;
+    bool return_invariant = 9;
 }
 
 enum PipelineStage {
@@ -211,11 +228,6 @@
     uint32 z = 3;
 }
 
-message ReturnLocation {
-    uint32 value = 1;
-    optional Interpolation interpolation = 2;
-}
-
 ////////////////////////////////////////////////////////////////////////////////
 // Blocks
 ////////////////////////////////////////////////////////////////////////////////
@@ -239,25 +251,27 @@
         InstructionDiscard discard = 7;
         InstructionLet let = 8;
         InstructionVar var = 9;
-        InstructionConstruct construct = 10;
-        InstructionConvert convert = 11;
-        InstructionAccess access = 12;
-        InstructionUserCall user_call = 13;
-        InstructionBuiltinCall builtin_call = 14;
-        InstructionLoad load = 15;
-        InstructionStore store = 16;
-        InstructionLoadVectorElement load_vector_element = 17;
-        InstructionStoreVectorElement store_vector_element = 18;
-        InstructionSwizzle swizzle = 19;
-        InstructionIf if = 20;
-        InstructionSwitch switch = 21;
-        InstructionLoop loop = 22;
-        InstructionExitIf exit_if = 23;
-        InstructionExitSwitch exit_switch = 24;
-        InstructionExitLoop exit_loop = 25;
-        InstructionNextIteration next_iteration = 26;
-        InstructionContinue continue = 27;
-        InstructionBreakIf break_if = 28;
+        InstructionBitcast bitcast = 10;
+        InstructionConstruct construct = 11;
+        InstructionConvert convert = 12;
+        InstructionAccess access = 13;
+        InstructionUserCall user_call = 14;
+        InstructionBuiltinCall builtin_call = 15;
+        InstructionLoad load = 16;
+        InstructionStore store = 17;
+        InstructionLoadVectorElement load_vector_element = 18;
+        InstructionStoreVectorElement store_vector_element = 19;
+        InstructionSwizzle swizzle = 20;
+        InstructionIf if = 21;
+        InstructionSwitch switch = 22;
+        InstructionLoop loop = 23;
+        InstructionExitIf exit_if = 24;
+        InstructionExitSwitch exit_switch = 25;
+        InstructionExitLoop exit_loop = 26;
+        InstructionNextIteration next_iteration = 27;
+        InstructionContinue continue = 28;
+        InstructionBreakIf break_if = 29;
+        InstructionUnreachable unreachable = 30;
     }
 }
 
@@ -273,7 +287,9 @@
 
 message InstructionBuiltin {}
 
-message InstructionConstructor {}
+message InstructionBitcast {}
+
+message InstructionConstruct {}
 
 message InstructionDiscard {}
 
@@ -283,8 +299,6 @@
     optional BindingPoint binding_point = 1;
 }
 
-message InstructionConstruct {}
-
 message InstructionConvert {}
 
 message InstructionAccess {}
@@ -345,6 +359,8 @@
 
 message InstructionBreakIf {}
 
+message InstructionUnreachable {}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Attributes
 ////////////////////////////////////////////////////////////////////////////////
@@ -357,11 +373,23 @@
     bool invariant = 6;
 }
 
+message AttributesFunctionParameter {
+    optional BuiltinValue builtin = 1;
+    optional Location location = 2;
+    optional BindingPoint binding_point = 3;
+    bool invariant = 4;
+}
+
 message Interpolation {
     InterpolationType type = 1;
     optional InterpolationSampling sampling = 2;
 }
 
+message Location {
+    uint32 value = 1;
+    optional Interpolation interpolation = 2;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Enums
 ////////////////////////////////////////////////////////////////////////////////
diff --git a/src/tint/lang/core/ir/binary/roundtrip_test.cc b/src/tint/lang/core/ir/binary/roundtrip_test.cc
index 3baf12b..cf6bbcc 100644
--- a/src/tint/lang/core/ir/binary/roundtrip_test.cc
+++ b/src/tint/lang/core/ir/binary/roundtrip_test.cc
@@ -30,7 +30,10 @@
 #include "src/tint/lang/core/ir/binary/decode.h"
 #include "src/tint/lang/core/ir/binary/encode.h"
 #include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/core/type/depth_multisampled_texture.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/multisampled_texture.h"
 #include "src/tint/lang/core/type/sampled_texture.h"
 #include "src/tint/lang/core/type/storage_texture.h"
 
@@ -46,11 +49,11 @@
     std::pair<std::string, std::string> Roundtrip() {
         auto pre = Disassemble(this->mod);
         auto encoded = Encode(this->mod);
-        if (!encoded) {
+        if (encoded != Success) {
             return {pre, encoded.Failure().reason.str()};
         }
         auto decoded = Decode(encoded->Slice());
-        if (!decoded) {
+        if (decoded != Success) {
             return {pre, decoded.Failure().reason.str()};
         }
         auto post = Disassemble(decoded.Get());
@@ -126,6 +129,27 @@
     RUN_TEST();
 }
 
+TEST_F(IRBinaryRoundtripTest, Fn_ParameterAttributes) {
+    auto* fn = b.Function("Function", ty.void_());
+    auto* p0 = b.FunctionParam(ty.i32());
+    auto* p1 = b.FunctionParam(ty.u32());
+    auto* p2 = b.FunctionParam(ty.f32());
+    auto* p3 = b.FunctionParam(ty.bool_());
+    p0->SetBuiltin(BuiltinValue::kGlobalInvocationId);
+    p1->SetInvariant(true);
+    p2->SetLocation(10, Interpolation{InterpolationType::kFlat, InterpolationSampling::kCenter});
+    p3->SetBindingPoint(20, 30);
+    fn->SetParams({p0, p1, p2, p3});
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, Fn_ReturnBuiltin) {
+    auto* fn = b.Function("Function", ty.void_());
+    fn->SetReturnBuiltin(BuiltinValue::kFragDepth);
+    b.ir.SetName(fn, "Function");
+    RUN_TEST();
+}
+
 TEST_F(IRBinaryRoundtripTest, Fn_ReturnLocation) {
     auto* fn = b.Function("Function", ty.void_());
     fn->SetReturnLocation(42, std::nullopt);
@@ -143,6 +167,13 @@
     RUN_TEST();
 }
 
+TEST_F(IRBinaryRoundtripTest, Fn_ReturnInvariant) {
+    auto* fn = b.Function("Function", ty.void_());
+    fn->SetReturnInvariant(true);
+    b.ir.SetName(fn, "Function");
+    RUN_TEST();
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // Types
 ////////////////////////////////////////////////////////////////////////////////
@@ -279,6 +310,19 @@
     RUN_TEST();
 }
 
+TEST_F(IRBinaryRoundtripTest, multisampled_texture) {
+    auto* tex =
+        ty.Get<core::type::MultisampledTexture>(core::type::TextureDimension::k2d, ty.f32());
+    b.Append(b.ir.root_block, [&] { b.Var(ty.ptr(handle, tex, read)); });
+    RUN_TEST();
+}
+
+TEST_F(IRBinaryRoundtripTest, depth_multisampled_texture) {
+    auto* tex = ty.Get<core::type::DepthMultisampledTexture>(core::type::TextureDimension::k2d);
+    b.Append(b.ir.root_block, [&] { b.Var(ty.ptr(handle, tex, read)); });
+    RUN_TEST();
+}
+
 TEST_F(IRBinaryRoundtripTest, storage_texture) {
     auto* tex = ty.Get<core::type::StorageTexture>(core::type::TextureDimension::k2dArray,
                                                    core::TexelFormat::kRg32Float,
@@ -287,6 +331,12 @@
     RUN_TEST();
 }
 
+TEST_F(IRBinaryRoundtripTest, external_texture) {
+    auto* tex = ty.Get<core::type::ExternalTexture>();
+    b.Append(b.ir.root_block, [&] { b.Var(ty.ptr(handle, tex, read)); });
+    RUN_TEST();
+}
+
 TEST_F(IRBinaryRoundtripTest, sampler) {
     auto* sampler = ty.Get<core::type::Sampler>(core::type::SamplerKind::kSampler);
     b.Append(b.ir.root_block, [&] { b.Var(ty.ptr(handle, sampler, read)); });
@@ -497,6 +547,14 @@
     RUN_TEST();
 }
 
+TEST_F(IRBinaryRoundtripTest, Bitcast) {
+    auto* x = b.FunctionParam<vec4<f32>>("x");
+    auto* fn = b.Function("Function", ty.vec4<u32>());
+    fn->SetParams({x});
+    b.Append(fn->Block(), [&] { b.Return(fn, b.Bitcast<vec4<u32>>(x)); });
+    RUN_TEST();
+}
+
 TEST_F(IRBinaryRoundtripTest, Convert) {
     auto* x = b.FunctionParam<vec4<f32>>("x");
     auto* fn = b.Function("Function", ty.vec4<u32>());
@@ -657,5 +715,12 @@
     });
     RUN_TEST();
 }
+
+TEST_F(IRBinaryRoundtripTest, Unreachable) {
+    auto* fn = b.Function("Function", ty.i32());
+    b.Append(fn->Block(), [&] { b.Unreachable(); });
+    RUN_TEST();
+}
+
 }  // namespace
 }  // namespace tint::core::ir::binary
diff --git a/src/tint/lang/core/ir/bitcast.cc b/src/tint/lang/core/ir/bitcast.cc
index 26031a47..8c6ece5 100644
--- a/src/tint/lang/core/ir/bitcast.cc
+++ b/src/tint/lang/core/ir/bitcast.cc
@@ -34,6 +34,8 @@
 
 namespace tint::core::ir {
 
+Bitcast::Bitcast() = default;
+
 Bitcast::Bitcast(InstructionResult* result, Value* val) {
     AddOperand(Bitcast::kValueOperandOffset, val);
     AddResult(result);
diff --git a/src/tint/lang/core/ir/bitcast.h b/src/tint/lang/core/ir/bitcast.h
index 3526681..ad30e92 100644
--- a/src/tint/lang/core/ir/bitcast.h
+++ b/src/tint/lang/core/ir/bitcast.h
@@ -41,6 +41,9 @@
     /// The offset in Operands() for the value
     static constexpr size_t kValueOperandOffset = 0;
 
+    /// Constructor (no results, no operands)
+    Bitcast();
+
     /// Constructor
     /// @param result the result value
     /// @param val the value being bitcast
diff --git a/src/tint/lang/core/ir/disassembler.cc b/src/tint/lang/core/ir/disassembler.cc
index 728004c..9292f50 100644
--- a/src/tint/lang/core/ir/disassembler.cc
+++ b/src/tint/lang/core/ir/disassembler.cc
@@ -847,10 +847,10 @@
             out_ << "gte";
             break;
         case BinaryOp::kShiftLeft:
-            out_ << "shiftl";
+            out_ << "shl";
             break;
         case BinaryOp::kShiftRight:
-            out_ << "shiftr";
+            out_ << "shr";
             break;
     }
     out_ << " ";
diff --git a/src/tint/lang/core/ir/function.cc b/src/tint/lang/core/ir/function.cc
index 8c095e0..ebe4211 100644
--- a/src/tint/lang/core/ir/function.cc
+++ b/src/tint/lang/core/ir/function.cc
@@ -93,16 +93,4 @@
     return "<unknown>";
 }
 
-std::string_view ToString(enum Function::ReturnBuiltin value) {
-    switch (value) {
-        case Function::ReturnBuiltin::kFragDepth:
-            return "frag_depth";
-        case Function::ReturnBuiltin::kSampleMask:
-            return "sample_mask";
-        case Function::ReturnBuiltin::kPosition:
-            return "position";
-    }
-    return "<unknown>";
-}
-
 }  // namespace tint::core::ir
diff --git a/src/tint/lang/core/ir/function.h b/src/tint/lang/core/ir/function.h
index 0193884..5cf533f 100644
--- a/src/tint/lang/core/ir/function.h
+++ b/src/tint/lang/core/ir/function.h
@@ -62,16 +62,6 @@
         kVertex,
     };
 
-    /// Builtin attached to return types
-    enum class ReturnBuiltin {
-        /// Builtin Position attribute
-        kPosition,
-        /// Builtin FragDepth attribute
-        kFragDepth,
-        /// Builtin SampleMask
-        kSampleMask,
-    };
-
     /// Constructor
     Function();
 
@@ -114,29 +104,37 @@
 
     /// Sets the return attributes
     /// @param builtin the builtin to set
-    void SetReturnBuiltin(ReturnBuiltin builtin) {
+    void SetReturnBuiltin(BuiltinValue builtin) {
         TINT_ASSERT(!return_.builtin.has_value());
         return_.builtin = builtin;
     }
     /// @returns the return builtin attribute
-    std::optional<enum ReturnBuiltin> ReturnBuiltin() const { return return_.builtin; }
+    std::optional<BuiltinValue> ReturnBuiltin() const { return return_.builtin; }
+
     /// Clears the return builtin attribute.
     void ClearReturnBuiltin() { return_.builtin = {}; }
 
     /// Sets the return location
+    /// @param location the location to set
+    void SetReturnLocation(Location location) { return_.location = std::move(location); }
+
+    /// Sets the return location
     /// @param loc the location to set
     /// @param interp the interpolation
     void SetReturnLocation(uint32_t loc, std::optional<core::Interpolation> interp) {
         return_.location = {loc, interp};
     }
+
     /// @returns the return location
     std::optional<Location> ReturnLocation() const { return return_.location; }
+
     /// Clears the return location attribute.
     void ClearReturnLocation() { return_.location = {}; }
 
     /// Sets the return as invariant
     /// @param val the invariant value to set
     void SetReturnInvariant(bool val) { return_.invariant = val; }
+
     /// @returns the return invariant value
     bool ReturnInvariant() const { return return_.invariant; }
 
@@ -176,7 +174,7 @@
 
     struct {
         const core::type::Type* type = nullptr;
-        std::optional<enum ReturnBuiltin> builtin;
+        std::optional<BuiltinValue> builtin;
         std::optional<Location> location;
         bool invariant = false;
     } return_;
@@ -197,18 +195,6 @@
     return out << ToString(value);
 }
 
-/// @param value the enum value
-/// @returns the string for the given enum value
-std::string_view ToString(enum Function::ReturnBuiltin value);
-
-/// @param out the stream to write to
-/// @param value the Function::ReturnBuiltin
-/// @returns @p out so calls can be chained
-template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
-auto& operator<<(STREAM& out, enum Function::ReturnBuiltin value) {
-    return out << ToString(value);
-}
-
 }  // namespace tint::core::ir
 
 #endif  // SRC_TINT_LANG_CORE_IR_FUNCTION_H_
diff --git a/src/tint/lang/core/ir/function_param.cc b/src/tint/lang/core/ir/function_param.cc
index 389942a..6dd00df 100644
--- a/src/tint/lang/core/ir/function_param.cc
+++ b/src/tint/lang/core/ir/function_param.cc
@@ -41,38 +41,6 @@
 
 FunctionParam::~FunctionParam() = default;
 
-std::string_view ToString(enum FunctionParam::Builtin value) {
-    switch (value) {
-        case FunctionParam::Builtin::kVertexIndex:
-            return "vertex_index";
-        case FunctionParam::Builtin::kInstanceIndex:
-            return "instance_index";
-        case FunctionParam::Builtin::kPosition:
-            return "position";
-        case FunctionParam::Builtin::kFrontFacing:
-            return "front_facing";
-        case FunctionParam::Builtin::kLocalInvocationId:
-            return "local_invocation_id";
-        case FunctionParam::Builtin::kLocalInvocationIndex:
-            return "local_invocation_index";
-        case FunctionParam::Builtin::kGlobalInvocationId:
-            return "global_invocation_id";
-        case FunctionParam::Builtin::kWorkgroupId:
-            return "workgroup_id";
-        case FunctionParam::Builtin::kNumWorkgroups:
-            return "num_workgroups";
-        case FunctionParam::Builtin::kSampleIndex:
-            return "sample_index";
-        case FunctionParam::Builtin::kSampleMask:
-            return "sample_mask";
-        case FunctionParam::Builtin::kSubgroupInvocationId:
-            return "subgroup_invocation_id";
-        case FunctionParam::Builtin::kSubgroupSize:
-            return "subgroup_size";
-    }
-    return "<unknown>";
-}
-
 FunctionParam* FunctionParam::Clone(CloneContext& ctx) {
     auto* out = ctx.ir.values.Create<FunctionParam>(type_);
     out->builtin_ = builtin_;
diff --git a/src/tint/lang/core/ir/function_param.h b/src/tint/lang/core/ir/function_param.h
index 4f8e22c..6cefb10 100644
--- a/src/tint/lang/core/ir/function_param.h
+++ b/src/tint/lang/core/ir/function_param.h
@@ -31,6 +31,7 @@
 #include <utility>
 
 #include "src/tint/api/common/binding_point.h"
+#include "src/tint/lang/core/builtin_value.h"
 #include "src/tint/lang/core/ir/location.h"
 #include "src/tint/lang/core/ir/value.h"
 #include "src/tint/utils/containers/vector.h"
@@ -42,36 +43,6 @@
 /// A function parameter in the IR.
 class FunctionParam : public Castable<FunctionParam, Value> {
   public:
-    /// Builtin attribute
-    enum class Builtin {
-        /// Builtin Vertex index
-        kVertexIndex,
-        /// Builtin Instance index
-        kInstanceIndex,
-        /// Builtin Position
-        kPosition,
-        /// Builtin FrontFacing
-        kFrontFacing,
-        /// Builtin Local invocation id
-        kLocalInvocationId,
-        /// Builtin Local invocation index
-        kLocalInvocationIndex,
-        /// Builtin Global invocation id
-        kGlobalInvocationId,
-        /// Builtin Workgroup id
-        kWorkgroupId,
-        /// Builtin Num workgroups
-        kNumWorkgroups,
-        /// Builtin Sample index
-        kSampleIndex,
-        /// Builtin Sample mask
-        kSampleMask,
-        /// Builtin Subgroup invocation id
-        kSubgroupInvocationId,
-        /// Builtin Subgroup size
-        kSubgroupSize,
-    };
-
     /// Constructor
     /// @param type the type of the var
     explicit FunctionParam(const core::type::Type* type);
@@ -85,29 +56,37 @@
 
     /// Sets the builtin information. Note, it is currently an error if the builtin is already set.
     /// @param val the builtin to set
-    void SetBuiltin(FunctionParam::Builtin val) {
+    void SetBuiltin(core::BuiltinValue val) {
         TINT_ASSERT(!builtin_.has_value());
         builtin_ = val;
     }
     /// @returns the builtin set for the parameter
-    std::optional<FunctionParam::Builtin> Builtin() const { return builtin_; }
+    std::optional<core::BuiltinValue> Builtin() const { return builtin_; }
+
     /// Clears the builtin attribute.
     void ClearBuiltin() { builtin_ = {}; }
 
     /// Sets the parameter as invariant
     /// @param val the value to set for invariant
     void SetInvariant(bool val) { invariant_ = val; }
+
     /// @returns true if parameter is invariant
     bool Invariant() const { return invariant_; }
 
     /// Sets the location
+    /// @param location the location
+    void SetLocation(ir::Location location) { location_ = std::move(location); }
+
+    /// Sets the location
     /// @param loc the location value
     /// @param interpolation if the location interpolation settings
     void SetLocation(uint32_t loc, std::optional<core::Interpolation> interpolation) {
         location_ = {loc, interpolation};
     }
+
     /// @returns the location if `Attributes` contains `kLocation`
-    std::optional<struct Location> Location() const { return location_; }
+    std::optional<ir::Location> Location() const { return location_; }
+
     /// Clears the location attribute.
     void ClearLocation() { location_ = {}; }
 
@@ -115,29 +94,18 @@
     /// @param group the group
     /// @param binding the binding
     void SetBindingPoint(uint32_t group, uint32_t binding) { binding_point_ = {group, binding}; }
+
     /// @returns the binding points if `Attributes` contains `kBindingPoint`
     std::optional<struct BindingPoint> BindingPoint() const { return binding_point_; }
 
   private:
     const core::type::Type* type_ = nullptr;
-    std::optional<enum FunctionParam::Builtin> builtin_;
+    std::optional<core::BuiltinValue> builtin_;
     std::optional<struct Location> location_;
     std::optional<struct BindingPoint> binding_point_;
     bool invariant_ = false;
 };
 
-/// @param value the enum value
-/// @returns the string for the given enum value
-std::string_view ToString(enum FunctionParam::Builtin value);
-
-/// @param out the stream to write to
-/// @param value the FunctionParam::Builtin
-/// @returns @p out so calls can be chained
-template <typename STREAM, typename = traits::EnableIfIsOStream<STREAM>>
-auto& operator<<(STREAM& out, enum FunctionParam::Builtin value) {
-    return out << ToString(value);
-}
-
 }  // namespace tint::core::ir
 
 #endif  // SRC_TINT_LANG_CORE_IR_FUNCTION_PARAM_H_
diff --git a/src/tint/lang/core/ir/function_param_test.cc b/src/tint/lang/core/ir/function_param_test.cc
index f4b709e..375e8a8 100644
--- a/src/tint/lang/core/ir/function_param_test.cc
+++ b/src/tint/lang/core/ir/function_param_test.cc
@@ -53,8 +53,8 @@
             Module mod;
             Builder b{mod};
             auto* fp = b.FunctionParam(mod.Types().f32());
-            fp->SetBuiltin(FunctionParam::Builtin::kVertexIndex);
-            fp->SetBuiltin(FunctionParam::Builtin::kSampleMask);
+            fp->SetBuiltin(BuiltinValue::kVertexIndex);
+            fp->SetBuiltin(BuiltinValue::kSampleMask);
         },
         "");
 }
@@ -72,7 +72,7 @@
 
 TEST_F(IR_FunctionParamTest, Clone) {
     auto* fp = b.FunctionParam(mod.Types().f32());
-    fp->SetBuiltin(FunctionParam::Builtin::kVertexIndex);
+    fp->SetBuiltin(BuiltinValue::kVertexIndex);
     fp->SetLocation(
         1, Interpolation{core::InterpolationType::kFlat, core::InterpolationSampling::kCentroid});
     fp->SetInvariant(true);
@@ -84,7 +84,7 @@
     EXPECT_EQ(new_fp->Type(), mod.Types().f32());
 
     EXPECT_TRUE(new_fp->Builtin().has_value());
-    EXPECT_EQ(FunctionParam::Builtin::kVertexIndex, new_fp->Builtin().value());
+    EXPECT_EQ(BuiltinValue::kVertexIndex, new_fp->Builtin().value());
 
     EXPECT_TRUE(new_fp->Location().has_value());
     auto loc = new_fp->Location();
diff --git a/src/tint/lang/core/ir/function_test.cc b/src/tint/lang/core/ir/function_test.cc
index 57156cf..8873648 100644
--- a/src/tint/lang/core/ir/function_test.cc
+++ b/src/tint/lang/core/ir/function_test.cc
@@ -53,8 +53,8 @@
             Module mod;
             Builder b{mod};
             auto* f = b.Function("my_func", mod.Types().void_());
-            f->SetReturnBuiltin(Function::ReturnBuiltin::kFragDepth);
-            f->SetReturnBuiltin(Function::ReturnBuiltin::kPosition);
+            f->SetReturnBuiltin(BuiltinValue::kFragDepth);
+            f->SetReturnBuiltin(BuiltinValue::kPosition);
         },
         "");
 }
@@ -84,7 +84,7 @@
 TEST_F(IR_FunctionTest, Clone) {
     auto* f =
         b.Function("my_func", mod.Types().i32(), Function::PipelineStage::kCompute, {{2, 3, 4}});
-    f->SetReturnBuiltin(Function::ReturnBuiltin::kFragDepth);
+    f->SetReturnBuiltin(BuiltinValue::kFragDepth);
     f->SetReturnLocation(
         1, Interpolation{core::InterpolationType::kFlat, core::InterpolationSampling::kCentroid});
     f->SetReturnInvariant(true);
@@ -110,7 +110,7 @@
     EXPECT_EQ(mod.Types().i32(), new_f->ReturnType());
 
     EXPECT_TRUE(new_f->ReturnBuiltin().has_value());
-    EXPECT_EQ(Function::ReturnBuiltin::kFragDepth, new_f->ReturnBuiltin().value());
+    EXPECT_EQ(BuiltinValue::kFragDepth, new_f->ReturnBuiltin().value());
 
     EXPECT_TRUE(new_f->ReturnLocation().has_value());
     auto loc = new_f->ReturnLocation().value();
diff --git a/src/tint/lang/core/ir/transform/add_empty_entry_point.cc b/src/tint/lang/core/ir/transform/add_empty_entry_point.cc
index d1b35b4..189bbfe 100644
--- a/src/tint/lang/core/ir/transform/add_empty_entry_point.cc
+++ b/src/tint/lang/core/ir/transform/add_empty_entry_point.cc
@@ -54,7 +54,7 @@
 
 Result<SuccessType> AddEmptyEntryPoint(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "AddEmptyEntryPoint transform");
-    if (!result) {
+    if (result != Success) {
         return result.Failure();
     }
 
diff --git a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
index 0d9b9fa..55b3321 100644
--- a/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/bgra8unorm_polyfill.cc
@@ -183,7 +183,7 @@
 
 Result<SuccessType> Bgra8UnormPolyfill(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "Bgra8UnormPolyfill transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/binary_polyfill.cc b/src/tint/lang/core/ir/transform/binary_polyfill.cc
index ba175c6..14c6756 100644
--- a/src/tint/lang/core/ir/transform/binary_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/binary_polyfill.cc
@@ -247,7 +247,7 @@
 
 Result<SuccessType> BinaryPolyfill(Module& ir, const BinaryPolyfillConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "BinaryPolyfill transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/binary_polyfill_test.cc b/src/tint/lang/core/ir/transform/binary_polyfill_test.cc
index 81e4272..b582411 100644
--- a/src/tint/lang/core/ir/transform/binary_polyfill_test.cc
+++ b/src/tint/lang/core/ir/transform/binary_polyfill_test.cc
@@ -63,11 +63,11 @@
 };
 
 TEST_F(IR_BinaryPolyfillTest, ShiftLeft_NoPolyfill) {
-    Build(BinaryOp::kShiftLeft, ty.i32(), ty.i32(), ty.i32());
+    Build(BinaryOp::kShiftLeft, ty.i32(), ty.i32(), ty.u32());
     auto* src = R"(
-%foo = func(%lhs:i32, %rhs:i32):i32 -> %b1 {
+%foo = func(%lhs:i32, %rhs:u32):i32 -> %b1 {
   %b1 = block {
-    %result:i32 = shiftl %lhs, %rhs
+    %result:i32 = shl %lhs, %rhs
     ret %result
   }
 }
@@ -83,11 +83,11 @@
 }
 
 TEST_F(IR_BinaryPolyfillTest, ShiftRight_NoPolyfill) {
-    Build(BinaryOp::kShiftRight, ty.i32(), ty.i32(), ty.i32());
+    Build(BinaryOp::kShiftRight, ty.i32(), ty.i32(), ty.u32());
     auto* src = R"(
-%foo = func(%lhs:i32, %rhs:i32):i32 -> %b1 {
+%foo = func(%lhs:i32, %rhs:u32):i32 -> %b1 {
   %b1 = block {
-    %result:i32 = shiftr %lhs, %rhs
+    %result:i32 = shr %lhs, %rhs
     ret %result
   }
 }
@@ -103,20 +103,20 @@
 }
 
 TEST_F(IR_BinaryPolyfillTest, ShiftLeft_I32) {
-    Build(BinaryOp::kShiftLeft, ty.i32(), ty.i32(), ty.i32());
+    Build(BinaryOp::kShiftLeft, ty.i32(), ty.i32(), ty.u32());
     auto* src = R"(
-%foo = func(%lhs:i32, %rhs:i32):i32 -> %b1 {
+%foo = func(%lhs:i32, %rhs:u32):i32 -> %b1 {
   %b1 = block {
-    %result:i32 = shiftl %lhs, %rhs
+    %result:i32 = shl %lhs, %rhs
     ret %result
   }
 }
 )";
     auto* expect = R"(
-%foo = func(%lhs:i32, %rhs:i32):i32 -> %b1 {
+%foo = func(%lhs:i32, %rhs:u32):i32 -> %b1 {
   %b1 = block {
-    %4:i32 = and %rhs, 31u
-    %result:i32 = shiftl %lhs, %4
+    %4:u32 = and %rhs, 31u
+    %result:i32 = shl %lhs, %4
     ret %result
   }
 }
@@ -135,7 +135,7 @@
     auto* src = R"(
 %foo = func(%lhs:u32, %rhs:u32):u32 -> %b1 {
   %b1 = block {
-    %result:u32 = shiftl %lhs, %rhs
+    %result:u32 = shl %lhs, %rhs
     ret %result
   }
 }
@@ -144,7 +144,7 @@
 %foo = func(%lhs:u32, %rhs:u32):u32 -> %b1 {
   %b1 = block {
     %4:u32 = and %rhs, 31u
-    %result:u32 = shiftl %lhs, %4
+    %result:u32 = shl %lhs, %4
     ret %result
   }
 }
@@ -159,20 +159,20 @@
 }
 
 TEST_F(IR_BinaryPolyfillTest, ShiftLeft_Vec2I32) {
-    Build(BinaryOp::kShiftLeft, ty.vec2<i32>(), ty.vec2<i32>(), ty.vec2<i32>());
+    Build(BinaryOp::kShiftLeft, ty.vec2<i32>(), ty.vec2<i32>(), ty.vec2<u32>());
     auto* src = R"(
-%foo = func(%lhs:vec2<i32>, %rhs:vec2<i32>):vec2<i32> -> %b1 {
+%foo = func(%lhs:vec2<i32>, %rhs:vec2<u32>):vec2<i32> -> %b1 {
   %b1 = block {
-    %result:vec2<i32> = shiftl %lhs, %rhs
+    %result:vec2<i32> = shl %lhs, %rhs
     ret %result
   }
 }
 )";
     auto* expect = R"(
-%foo = func(%lhs:vec2<i32>, %rhs:vec2<i32>):vec2<i32> -> %b1 {
+%foo = func(%lhs:vec2<i32>, %rhs:vec2<u32>):vec2<i32> -> %b1 {
   %b1 = block {
-    %4:vec2<i32> = and %rhs, vec2<u32>(31u)
-    %result:vec2<i32> = shiftl %lhs, %4
+    %4:vec2<u32> = and %rhs, vec2<u32>(31u)
+    %result:vec2<i32> = shl %lhs, %4
     ret %result
   }
 }
@@ -191,7 +191,7 @@
     auto* src = R"(
 %foo = func(%lhs:vec3<u32>, %rhs:vec3<u32>):vec3<u32> -> %b1 {
   %b1 = block {
-    %result:vec3<u32> = shiftl %lhs, %rhs
+    %result:vec3<u32> = shl %lhs, %rhs
     ret %result
   }
 }
@@ -200,7 +200,7 @@
 %foo = func(%lhs:vec3<u32>, %rhs:vec3<u32>):vec3<u32> -> %b1 {
   %b1 = block {
     %4:vec3<u32> = and %rhs, vec3<u32>(31u)
-    %result:vec3<u32> = shiftl %lhs, %4
+    %result:vec3<u32> = shl %lhs, %4
     ret %result
   }
 }
@@ -215,20 +215,20 @@
 }
 
 TEST_F(IR_BinaryPolyfillTest, ShiftRight_I32) {
-    Build(BinaryOp::kShiftRight, ty.i32(), ty.i32(), ty.i32());
+    Build(BinaryOp::kShiftRight, ty.i32(), ty.i32(), ty.u32());
     auto* src = R"(
-%foo = func(%lhs:i32, %rhs:i32):i32 -> %b1 {
+%foo = func(%lhs:i32, %rhs:u32):i32 -> %b1 {
   %b1 = block {
-    %result:i32 = shiftr %lhs, %rhs
+    %result:i32 = shr %lhs, %rhs
     ret %result
   }
 }
 )";
     auto* expect = R"(
-%foo = func(%lhs:i32, %rhs:i32):i32 -> %b1 {
+%foo = func(%lhs:i32, %rhs:u32):i32 -> %b1 {
   %b1 = block {
-    %4:i32 = and %rhs, 31u
-    %result:i32 = shiftr %lhs, %4
+    %4:u32 = and %rhs, 31u
+    %result:i32 = shr %lhs, %4
     ret %result
   }
 }
@@ -247,7 +247,7 @@
     auto* src = R"(
 %foo = func(%lhs:u32, %rhs:u32):u32 -> %b1 {
   %b1 = block {
-    %result:u32 = shiftr %lhs, %rhs
+    %result:u32 = shr %lhs, %rhs
     ret %result
   }
 }
@@ -256,7 +256,7 @@
 %foo = func(%lhs:u32, %rhs:u32):u32 -> %b1 {
   %b1 = block {
     %4:u32 = and %rhs, 31u
-    %result:u32 = shiftr %lhs, %4
+    %result:u32 = shr %lhs, %4
     ret %result
   }
 }
@@ -271,20 +271,20 @@
 }
 
 TEST_F(IR_BinaryPolyfillTest, ShiftRight_Vec2I32) {
-    Build(BinaryOp::kShiftRight, ty.vec2<i32>(), ty.vec2<i32>(), ty.vec2<i32>());
+    Build(BinaryOp::kShiftRight, ty.vec2<i32>(), ty.vec2<i32>(), ty.vec2<u32>());
     auto* src = R"(
-%foo = func(%lhs:vec2<i32>, %rhs:vec2<i32>):vec2<i32> -> %b1 {
+%foo = func(%lhs:vec2<i32>, %rhs:vec2<u32>):vec2<i32> -> %b1 {
   %b1 = block {
-    %result:vec2<i32> = shiftr %lhs, %rhs
+    %result:vec2<i32> = shr %lhs, %rhs
     ret %result
   }
 }
 )";
     auto* expect = R"(
-%foo = func(%lhs:vec2<i32>, %rhs:vec2<i32>):vec2<i32> -> %b1 {
+%foo = func(%lhs:vec2<i32>, %rhs:vec2<u32>):vec2<i32> -> %b1 {
   %b1 = block {
-    %4:vec2<i32> = and %rhs, vec2<u32>(31u)
-    %result:vec2<i32> = shiftr %lhs, %4
+    %4:vec2<u32> = and %rhs, vec2<u32>(31u)
+    %result:vec2<i32> = shr %lhs, %4
     ret %result
   }
 }
@@ -303,7 +303,7 @@
     auto* src = R"(
 %foo = func(%lhs:vec3<u32>, %rhs:vec3<u32>):vec3<u32> -> %b1 {
   %b1 = block {
-    %result:vec3<u32> = shiftr %lhs, %rhs
+    %result:vec3<u32> = shr %lhs, %rhs
     ret %result
   }
 }
@@ -312,7 +312,7 @@
 %foo = func(%lhs:vec3<u32>, %rhs:vec3<u32>):vec3<u32> -> %b1 {
   %b1 = block {
     %4:vec3<u32> = and %rhs, vec3<u32>(31u)
-    %result:vec3<u32> = shiftr %lhs, %4
+    %result:vec3<u32> = shr %lhs, %4
     ret %result
   }
 }
diff --git a/src/tint/lang/core/ir/transform/binding_remapper.cc b/src/tint/lang/core/ir/transform/binding_remapper.cc
index 35a93f4..47d65ff 100644
--- a/src/tint/lang/core/ir/transform/binding_remapper.cc
+++ b/src/tint/lang/core/ir/transform/binding_remapper.cc
@@ -78,7 +78,7 @@
     Module& ir,
     const std::unordered_map<BindingPoint, BindingPoint>& binding_points) {
     auto result = ValidateAndDumpIfNeeded(ir, "BindingRemapper transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/block_decorated_structs.cc b/src/tint/lang/core/ir/transform/block_decorated_structs.cc
index 91d4b3d..bc8fe39 100644
--- a/src/tint/lang/core/ir/transform/block_decorated_structs.cc
+++ b/src/tint/lang/core/ir/transform/block_decorated_structs.cc
@@ -107,7 +107,7 @@
 
 Result<SuccessType> BlockDecoratedStructs(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "BlockDecoratedStructs transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill.cc b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
index 50718f6..951765e 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill.cc
@@ -640,7 +640,7 @@
 
 Result<SuccessType> BuiltinPolyfill(Module& ir, const BuiltinPolyfillConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "BuiltinPolyfill transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc b/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc
index 39e1168..63b015e 100644
--- a/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc
+++ b/src/tint/lang/core/ir/transform/builtin_polyfill_test.cc
@@ -223,16 +223,16 @@
   %b1 = block {
     %3:bool = lte %arg, 65535u
     %4:u32 = select 0u, 16u, %3
-    %5:u32 = shiftl %arg, %4
+    %5:u32 = shl %arg, %4
     %6:bool = lte %5, 16777215u
     %7:u32 = select 0u, 8u, %6
-    %8:u32 = shiftl %5, %7
+    %8:u32 = shl %5, %7
     %9:bool = lte %8, 268435455u
     %10:u32 = select 0u, 4u, %9
-    %11:u32 = shiftl %8, %10
+    %11:u32 = shl %8, %10
     %12:bool = lte %11, 1073741823u
     %13:u32 = select 0u, 2u, %12
-    %14:u32 = shiftl %11, %13
+    %14:u32 = shl %11, %13
     %15:bool = lte %14, 2147483647u
     %16:u32 = select 0u, 1u, %15
     %17:bool = eq %14, 0u
@@ -272,16 +272,16 @@
     %3:u32 = bitcast %arg
     %4:bool = lte %3, 65535u
     %5:u32 = select 0u, 16u, %4
-    %6:u32 = shiftl %3, %5
+    %6:u32 = shl %3, %5
     %7:bool = lte %6, 16777215u
     %8:u32 = select 0u, 8u, %7
-    %9:u32 = shiftl %6, %8
+    %9:u32 = shl %6, %8
     %10:bool = lte %9, 268435455u
     %11:u32 = select 0u, 4u, %10
-    %12:u32 = shiftl %9, %11
+    %12:u32 = shl %9, %11
     %13:bool = lte %12, 1073741823u
     %14:u32 = select 0u, 2u, %13
-    %15:u32 = shiftl %12, %14
+    %15:u32 = shl %12, %14
     %16:bool = lte %15, 2147483647u
     %17:u32 = select 0u, 1u, %16
     %18:bool = eq %15, 0u
@@ -321,16 +321,16 @@
   %b1 = block {
     %3:vec2<bool> = lte %arg, vec2<u32>(65535u)
     %4:vec2<u32> = select vec2<u32>(0u), vec2<u32>(16u), %3
-    %5:vec2<u32> = shiftl %arg, %4
+    %5:vec2<u32> = shl %arg, %4
     %6:vec2<bool> = lte %5, vec2<u32>(16777215u)
     %7:vec2<u32> = select vec2<u32>(0u), vec2<u32>(8u), %6
-    %8:vec2<u32> = shiftl %5, %7
+    %8:vec2<u32> = shl %5, %7
     %9:vec2<bool> = lte %8, vec2<u32>(268435455u)
     %10:vec2<u32> = select vec2<u32>(0u), vec2<u32>(4u), %9
-    %11:vec2<u32> = shiftl %8, %10
+    %11:vec2<u32> = shl %8, %10
     %12:vec2<bool> = lte %11, vec2<u32>(1073741823u)
     %13:vec2<u32> = select vec2<u32>(0u), vec2<u32>(2u), %12
-    %14:vec2<u32> = shiftl %11, %13
+    %14:vec2<u32> = shl %11, %13
     %15:vec2<bool> = lte %14, vec2<u32>(2147483647u)
     %16:vec2<u32> = select vec2<u32>(0u), vec2<u32>(1u), %15
     %17:vec2<bool> = eq %14, vec2<u32>(0u)
@@ -370,16 +370,16 @@
     %3:vec4<u32> = bitcast %arg
     %4:vec4<bool> = lte %3, vec4<u32>(65535u)
     %5:vec4<u32> = select vec4<u32>(0u), vec4<u32>(16u), %4
-    %6:vec4<u32> = shiftl %3, %5
+    %6:vec4<u32> = shl %3, %5
     %7:vec4<bool> = lte %6, vec4<u32>(16777215u)
     %8:vec4<u32> = select vec4<u32>(0u), vec4<u32>(8u), %7
-    %9:vec4<u32> = shiftl %6, %8
+    %9:vec4<u32> = shl %6, %8
     %10:vec4<bool> = lte %9, vec4<u32>(268435455u)
     %11:vec4<u32> = select vec4<u32>(0u), vec4<u32>(4u), %10
-    %12:vec4<u32> = shiftl %9, %11
+    %12:vec4<u32> = shl %9, %11
     %13:vec4<bool> = lte %12, vec4<u32>(1073741823u)
     %14:vec4<u32> = select vec4<u32>(0u), vec4<u32>(2u), %13
-    %15:vec4<u32> = shiftl %12, %14
+    %15:vec4<u32> = shl %12, %14
     %16:vec4<bool> = lte %15, vec4<u32>(2147483647u)
     %17:vec4<u32> = select vec4<u32>(0u), vec4<u32>(1u), %16
     %18:vec4<bool> = eq %15, vec4<u32>(0u)
@@ -440,19 +440,19 @@
     %3:u32 = and %arg, 65535u
     %4:bool = eq %3, 0u
     %5:u32 = select 0u, 16u, %4
-    %6:u32 = shiftr %arg, %5
+    %6:u32 = shr %arg, %5
     %7:u32 = and %6, 255u
     %8:bool = eq %7, 0u
     %9:u32 = select 0u, 8u, %8
-    %10:u32 = shiftr %6, %9
+    %10:u32 = shr %6, %9
     %11:u32 = and %10, 15u
     %12:bool = eq %11, 0u
     %13:u32 = select 0u, 4u, %12
-    %14:u32 = shiftr %10, %13
+    %14:u32 = shr %10, %13
     %15:u32 = and %14, 3u
     %16:bool = eq %15, 0u
     %17:u32 = select 0u, 2u, %16
-    %18:u32 = shiftr %14, %17
+    %18:u32 = shr %14, %17
     %19:u32 = and %18, 1u
     %20:bool = eq %19, 0u
     %21:u32 = select 0u, 1u, %20
@@ -493,19 +493,19 @@
     %4:u32 = and %3, 65535u
     %5:bool = eq %4, 0u
     %6:u32 = select 0u, 16u, %5
-    %7:u32 = shiftr %3, %6
+    %7:u32 = shr %3, %6
     %8:u32 = and %7, 255u
     %9:bool = eq %8, 0u
     %10:u32 = select 0u, 8u, %9
-    %11:u32 = shiftr %7, %10
+    %11:u32 = shr %7, %10
     %12:u32 = and %11, 15u
     %13:bool = eq %12, 0u
     %14:u32 = select 0u, 4u, %13
-    %15:u32 = shiftr %11, %14
+    %15:u32 = shr %11, %14
     %16:u32 = and %15, 3u
     %17:bool = eq %16, 0u
     %18:u32 = select 0u, 2u, %17
-    %19:u32 = shiftr %15, %18
+    %19:u32 = shr %15, %18
     %20:u32 = and %19, 1u
     %21:bool = eq %20, 0u
     %22:u32 = select 0u, 1u, %21
@@ -546,19 +546,19 @@
     %3:vec2<u32> = and %arg, vec2<u32>(65535u)
     %4:vec2<bool> = eq %3, vec2<u32>(0u)
     %5:vec2<u32> = select vec2<u32>(0u), vec2<u32>(16u), %4
-    %6:vec2<u32> = shiftr %arg, %5
+    %6:vec2<u32> = shr %arg, %5
     %7:vec2<u32> = and %6, vec2<u32>(255u)
     %8:vec2<bool> = eq %7, vec2<u32>(0u)
     %9:vec2<u32> = select vec2<u32>(0u), vec2<u32>(8u), %8
-    %10:vec2<u32> = shiftr %6, %9
+    %10:vec2<u32> = shr %6, %9
     %11:vec2<u32> = and %10, vec2<u32>(15u)
     %12:vec2<bool> = eq %11, vec2<u32>(0u)
     %13:vec2<u32> = select vec2<u32>(0u), vec2<u32>(4u), %12
-    %14:vec2<u32> = shiftr %10, %13
+    %14:vec2<u32> = shr %10, %13
     %15:vec2<u32> = and %14, vec2<u32>(3u)
     %16:vec2<bool> = eq %15, vec2<u32>(0u)
     %17:vec2<u32> = select vec2<u32>(0u), vec2<u32>(2u), %16
-    %18:vec2<u32> = shiftr %14, %17
+    %18:vec2<u32> = shr %14, %17
     %19:vec2<u32> = and %18, vec2<u32>(1u)
     %20:vec2<bool> = eq %19, vec2<u32>(0u)
     %21:vec2<u32> = select vec2<u32>(0u), vec2<u32>(1u), %20
@@ -787,19 +787,19 @@
     %3:u32 = and %arg, 4294901760u
     %4:bool = eq %3, 0u
     %5:u32 = select 16u, 0u, %4
-    %6:u32 = shiftr %arg, %5
+    %6:u32 = shr %arg, %5
     %7:u32 = and %6, 65280u
     %8:bool = eq %7, 0u
     %9:u32 = select 8u, 0u, %8
-    %10:u32 = shiftr %6, %9
+    %10:u32 = shr %6, %9
     %11:u32 = and %10, 240u
     %12:bool = eq %11, 0u
     %13:u32 = select 4u, 0u, %12
-    %14:u32 = shiftr %10, %13
+    %14:u32 = shr %10, %13
     %15:u32 = and %14, 12u
     %16:bool = eq %15, 0u
     %17:u32 = select 2u, 0u, %16
-    %18:u32 = shiftr %14, %17
+    %18:u32 = shr %14, %17
     %19:u32 = and %18, 2u
     %20:bool = eq %19, 0u
     %21:u32 = select 1u, 0u, %20
@@ -842,19 +842,19 @@
     %7:u32 = and %6, 4294901760u
     %8:bool = eq %7, 0u
     %9:u32 = select 16u, 0u, %8
-    %10:u32 = shiftr %6, %9
+    %10:u32 = shr %6, %9
     %11:u32 = and %10, 65280u
     %12:bool = eq %11, 0u
     %13:u32 = select 8u, 0u, %12
-    %14:u32 = shiftr %10, %13
+    %14:u32 = shr %10, %13
     %15:u32 = and %14, 240u
     %16:bool = eq %15, 0u
     %17:u32 = select 4u, 0u, %16
-    %18:u32 = shiftr %14, %17
+    %18:u32 = shr %14, %17
     %19:u32 = and %18, 12u
     %20:bool = eq %19, 0u
     %21:u32 = select 2u, 0u, %20
-    %22:u32 = shiftr %18, %21
+    %22:u32 = shr %18, %21
     %23:u32 = and %22, 2u
     %24:bool = eq %23, 0u
     %25:u32 = select 1u, 0u, %24
@@ -894,19 +894,19 @@
     %3:vec2<u32> = and %arg, vec2<u32>(4294901760u)
     %4:vec2<bool> = eq %3, vec2<u32>(0u)
     %5:vec2<u32> = select vec2<u32>(16u), vec2<u32>(0u), %4
-    %6:vec2<u32> = shiftr %arg, %5
+    %6:vec2<u32> = shr %arg, %5
     %7:vec2<u32> = and %6, vec2<u32>(65280u)
     %8:vec2<bool> = eq %7, vec2<u32>(0u)
     %9:vec2<u32> = select vec2<u32>(8u), vec2<u32>(0u), %8
-    %10:vec2<u32> = shiftr %6, %9
+    %10:vec2<u32> = shr %6, %9
     %11:vec2<u32> = and %10, vec2<u32>(240u)
     %12:vec2<bool> = eq %11, vec2<u32>(0u)
     %13:vec2<u32> = select vec2<u32>(4u), vec2<u32>(0u), %12
-    %14:vec2<u32> = shiftr %10, %13
+    %14:vec2<u32> = shr %10, %13
     %15:vec2<u32> = and %14, vec2<u32>(12u)
     %16:vec2<bool> = eq %15, vec2<u32>(0u)
     %17:vec2<u32> = select vec2<u32>(2u), vec2<u32>(0u), %16
-    %18:vec2<u32> = shiftr %14, %17
+    %18:vec2<u32> = shr %14, %17
     %19:vec2<u32> = and %18, vec2<u32>(2u)
     %20:vec2<bool> = eq %19, vec2<u32>(0u)
     %21:vec2<u32> = select vec2<u32>(1u), vec2<u32>(0u), %20
@@ -949,19 +949,19 @@
     %7:vec4<u32> = and %6, vec4<u32>(4294901760u)
     %8:vec4<bool> = eq %7, vec4<u32>(0u)
     %9:vec4<u32> = select vec4<u32>(16u), vec4<u32>(0u), %8
-    %10:vec4<u32> = shiftr %6, %9
+    %10:vec4<u32> = shr %6, %9
     %11:vec4<u32> = and %10, vec4<u32>(65280u)
     %12:vec4<bool> = eq %11, vec4<u32>(0u)
     %13:vec4<u32> = select vec4<u32>(8u), vec4<u32>(0u), %12
-    %14:vec4<u32> = shiftr %10, %13
+    %14:vec4<u32> = shr %10, %13
     %15:vec4<u32> = and %14, vec4<u32>(240u)
     %16:vec4<bool> = eq %15, vec4<u32>(0u)
     %17:vec4<u32> = select vec4<u32>(4u), vec4<u32>(0u), %16
-    %18:vec4<u32> = shiftr %14, %17
+    %18:vec4<u32> = shr %14, %17
     %19:vec4<u32> = and %18, vec4<u32>(12u)
     %20:vec4<bool> = eq %19, vec4<u32>(0u)
     %21:vec4<u32> = select vec4<u32>(2u), vec4<u32>(0u), %20
-    %22:vec4<u32> = shiftr %18, %21
+    %22:vec4<u32> = shr %18, %21
     %23:vec4<u32> = and %22, vec4<u32>(2u)
     %24:vec4<bool> = eq %23, vec4<u32>(0u)
     %25:vec4<u32> = select vec4<u32>(1u), vec4<u32>(0u), %24
@@ -1021,19 +1021,19 @@
     %3:u32 = and %arg, 65535u
     %4:bool = eq %3, 0u
     %5:u32 = select 0u, 16u, %4
-    %6:u32 = shiftr %arg, %5
+    %6:u32 = shr %arg, %5
     %7:u32 = and %6, 255u
     %8:bool = eq %7, 0u
     %9:u32 = select 0u, 8u, %8
-    %10:u32 = shiftr %6, %9
+    %10:u32 = shr %6, %9
     %11:u32 = and %10, 15u
     %12:bool = eq %11, 0u
     %13:u32 = select 0u, 4u, %12
-    %14:u32 = shiftr %10, %13
+    %14:u32 = shr %10, %13
     %15:u32 = and %14, 3u
     %16:bool = eq %15, 0u
     %17:u32 = select 0u, 2u, %16
-    %18:u32 = shiftr %14, %17
+    %18:u32 = shr %14, %17
     %19:u32 = and %18, 1u
     %20:bool = eq %19, 0u
     %21:u32 = select 0u, 1u, %20
@@ -1073,19 +1073,19 @@
     %4:u32 = and %3, 65535u
     %5:bool = eq %4, 0u
     %6:u32 = select 0u, 16u, %5
-    %7:u32 = shiftr %3, %6
+    %7:u32 = shr %3, %6
     %8:u32 = and %7, 255u
     %9:bool = eq %8, 0u
     %10:u32 = select 0u, 8u, %9
-    %11:u32 = shiftr %7, %10
+    %11:u32 = shr %7, %10
     %12:u32 = and %11, 15u
     %13:bool = eq %12, 0u
     %14:u32 = select 0u, 4u, %13
-    %15:u32 = shiftr %11, %14
+    %15:u32 = shr %11, %14
     %16:u32 = and %15, 3u
     %17:bool = eq %16, 0u
     %18:u32 = select 0u, 2u, %17
-    %19:u32 = shiftr %15, %18
+    %19:u32 = shr %15, %18
     %20:u32 = and %19, 1u
     %21:bool = eq %20, 0u
     %22:u32 = select 0u, 1u, %21
@@ -1125,19 +1125,19 @@
     %3:vec2<u32> = and %arg, vec2<u32>(65535u)
     %4:vec2<bool> = eq %3, vec2<u32>(0u)
     %5:vec2<u32> = select vec2<u32>(0u), vec2<u32>(16u), %4
-    %6:vec2<u32> = shiftr %arg, %5
+    %6:vec2<u32> = shr %arg, %5
     %7:vec2<u32> = and %6, vec2<u32>(255u)
     %8:vec2<bool> = eq %7, vec2<u32>(0u)
     %9:vec2<u32> = select vec2<u32>(0u), vec2<u32>(8u), %8
-    %10:vec2<u32> = shiftr %6, %9
+    %10:vec2<u32> = shr %6, %9
     %11:vec2<u32> = and %10, vec2<u32>(15u)
     %12:vec2<bool> = eq %11, vec2<u32>(0u)
     %13:vec2<u32> = select vec2<u32>(0u), vec2<u32>(4u), %12
-    %14:vec2<u32> = shiftr %10, %13
+    %14:vec2<u32> = shr %10, %13
     %15:vec2<u32> = and %14, vec2<u32>(3u)
     %16:vec2<bool> = eq %15, vec2<u32>(0u)
     %17:vec2<u32> = select vec2<u32>(0u), vec2<u32>(2u), %16
-    %18:vec2<u32> = shiftr %14, %17
+    %18:vec2<u32> = shr %14, %17
     %19:vec2<u32> = and %18, vec2<u32>(1u)
     %20:vec2<bool> = eq %19, vec2<u32>(0u)
     %21:vec2<u32> = select vec2<u32>(0u), vec2<u32>(1u), %20
@@ -1177,19 +1177,19 @@
     %4:vec4<u32> = and %3, vec4<u32>(65535u)
     %5:vec4<bool> = eq %4, vec4<u32>(0u)
     %6:vec4<u32> = select vec4<u32>(0u), vec4<u32>(16u), %5
-    %7:vec4<u32> = shiftr %3, %6
+    %7:vec4<u32> = shr %3, %6
     %8:vec4<u32> = and %7, vec4<u32>(255u)
     %9:vec4<bool> = eq %8, vec4<u32>(0u)
     %10:vec4<u32> = select vec4<u32>(0u), vec4<u32>(8u), %9
-    %11:vec4<u32> = shiftr %7, %10
+    %11:vec4<u32> = shr %7, %10
     %12:vec4<u32> = and %11, vec4<u32>(15u)
     %13:vec4<bool> = eq %12, vec4<u32>(0u)
     %14:vec4<u32> = select vec4<u32>(0u), vec4<u32>(4u), %13
-    %15:vec4<u32> = shiftr %11, %14
+    %15:vec4<u32> = shr %11, %14
     %16:vec4<u32> = and %15, vec4<u32>(3u)
     %17:vec4<bool> = eq %16, vec4<u32>(0u)
     %18:vec4<u32> = select vec4<u32>(0u), vec4<u32>(2u), %17
-    %19:vec4<u32> = shiftr %15, %18
+    %19:vec4<u32> = shr %15, %18
     %20:vec4<u32> = and %19, vec4<u32>(1u)
     %21:vec4<bool> = eq %20, vec4<u32>(0u)
     %22:vec4<u32> = select vec4<u32>(0u), vec4<u32>(1u), %21
@@ -1432,7 +1432,7 @@
     %3:vec4<u32> = construct 0u, 8u, 16u, 24u
     %4:vec4<i32> = construct 255i
     %5:vec4<i32> = and %arg, %4
-    %6:vec4<i32> = shiftl %5, %3
+    %6:vec4<i32> = shl %5, %3
     %7:vec4<u32> = convert %6
     %8:vec4<u32> = construct 1u
     %result:u32 = dot %7, %8
@@ -1467,7 +1467,7 @@
     %3:vec4<u32> = construct 0u, 8u, 16u, 24u
     %4:vec4<u32> = construct 255u
     %5:vec4<u32> = and %arg, %4
-    %6:vec4<u32> = shiftl %5, %3
+    %6:vec4<u32> = shl %5, %3
     %7:vec4<u32> = construct 1u
     %result:u32 = dot %6, %7
     ret %result
diff --git a/src/tint/lang/core/ir/transform/combine_access_instructions.cc b/src/tint/lang/core/ir/transform/combine_access_instructions.cc
index da304ea..7d82da4 100644
--- a/src/tint/lang/core/ir/transform/combine_access_instructions.cc
+++ b/src/tint/lang/core/ir/transform/combine_access_instructions.cc
@@ -81,7 +81,7 @@
 
 Result<SuccessType> CombineAccessInstructions(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "CombineAccessInstructions transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/conversion_polyfill.cc b/src/tint/lang/core/ir/transform/conversion_polyfill.cc
index f99ea69..705d946 100644
--- a/src/tint/lang/core/ir/transform/conversion_polyfill.cc
+++ b/src/tint/lang/core/ir/transform/conversion_polyfill.cc
@@ -228,7 +228,7 @@
 
 Result<SuccessType> ConversionPolyfill(Module& ir, const ConversionPolyfillConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "ConversionPolyfill transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/demote_to_helper.cc b/src/tint/lang/core/ir/transform/demote_to_helper.cc
index 89bb789..6c61cbc 100644
--- a/src/tint/lang/core/ir/transform/demote_to_helper.cc
+++ b/src/tint/lang/core/ir/transform/demote_to_helper.cc
@@ -217,7 +217,7 @@
 
 Result<SuccessType> DemoteToHelper(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "DemoteToHelper transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/demote_to_helper_test.cc b/src/tint/lang/core/ir/transform/demote_to_helper_test.cc
index 19b04dc..effd5ea 100644
--- a/src/tint/lang/core/ir/transform/demote_to_helper_test.cc
+++ b/src/tint/lang/core/ir/transform/demote_to_helper_test.cc
@@ -82,7 +82,7 @@
     mod.root_block->Append(buffer);
 
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(BuiltinValue::kFrontFacing);
     auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
     ep->SetParams({front_facing});
     ep->SetReturnLocation(0_u, {});
@@ -167,7 +167,7 @@
     });
 
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(BuiltinValue::kFrontFacing);
     auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
     ep->SetParams({front_facing});
     ep->SetReturnLocation(0_u, {});
@@ -270,7 +270,7 @@
     });
 
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(BuiltinValue::kFrontFacing);
     auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
     ep->SetParams({front_facing});
     ep->SetReturnLocation(0_u, {});
@@ -370,7 +370,7 @@
     });
 
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(BuiltinValue::kFrontFacing);
     auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
     ep->SetParams({front_facing});
     ep->SetReturnLocation(0_u, {});
@@ -453,7 +453,7 @@
 TEST_F(IR_DemoteToHelperTest, WriteToInvocationPrivateAddressSpace) {
     auto* priv = mod.root_block->Append(b.Var("priv", ty.ptr<private_, i32>()));
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(BuiltinValue::kFrontFacing);
     auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
     ep->SetParams({front_facing});
     ep->SetReturnLocation(0_u, {});
@@ -537,7 +537,7 @@
     mod.root_block->Append(texture);
 
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(BuiltinValue::kFrontFacing);
     auto* coord = b.FunctionParam("coord", ty.vec2<i32>());
     auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
     ep->SetParams({front_facing, coord});
@@ -620,7 +620,7 @@
     mod.root_block->Append(buffer);
 
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(BuiltinValue::kFrontFacing);
     auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
     ep->SetParams({front_facing});
     ep->SetReturnLocation(0_u, {});
@@ -699,7 +699,7 @@
     mod.root_block->Append(buffer);
 
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(BuiltinValue::kFrontFacing);
     auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
     ep->SetParams({front_facing});
     ep->SetReturnLocation(0_u, {});
@@ -782,7 +782,7 @@
     mod.root_block->Append(buffer);
 
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(BuiltinValue::kFrontFacing);
     auto* ep = b.Function("ep", ty.f32(), Function::PipelineStage::kFragment);
     ep->SetParams({front_facing});
     ep->SetReturnLocation(0_u, {});
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access.cc b/src/tint/lang/core/ir/transform/direct_variable_access.cc
index 0c2c27b..12b078b 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access.cc
+++ b/src/tint/lang/core/ir/transform/direct_variable_access.cc
@@ -666,7 +666,7 @@
 
 Result<SuccessType> DirectVariableAccess(Module& ir, const DirectVariableAccessOptions& options) {
     auto result = ValidateAndDumpIfNeeded(ir, "DirectVariableAccess transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/direct_variable_access_wgsl_test.cc b/src/tint/lang/core/ir/transform/direct_variable_access_wgsl_test.cc
index 31f65e1..9f39916 100644
--- a/src/tint/lang/core/ir/transform/direct_variable_access_wgsl_test.cc
+++ b/src/tint/lang/core/ir/transform/direct_variable_access_wgsl_test.cc
@@ -68,12 +68,12 @@
         }
 
         auto module = wgsl::reader::ProgramToIR(program);
-        if (!module) {
+        if (module != Success) {
             return "ProgramToIR() failed:\n" + module.Failure().reason.str();
         }
 
         auto res = DirectVariableAccess(module.Get(), options);
-        if (!res) {
+        if (res != Success) {
             return "DirectVariableAccess failed:\n" + res.Failure().reason.str();
         }
 
@@ -88,7 +88,7 @@
         }
 
         auto output = wgsl::writer::Generate(transformed, wgsl::writer::Options{});
-        if (!output) {
+        if (output != Success) {
             return "wgsl::writer::Generate() failed: \n" + output.Failure().reason.str();
         }
 
diff --git a/src/tint/lang/core/ir/transform/helper_test.h b/src/tint/lang/core/ir/transform/helper_test.h
index 5487534..6b1bdfa 100644
--- a/src/tint/lang/core/ir/transform/helper_test.h
+++ b/src/tint/lang/core/ir/transform/helper_test.h
@@ -51,14 +51,13 @@
     void Run(TRANSFORM&& transform_func, ARGS&&... args) {
         // Run the transform.
         auto result = transform_func(mod, args...);
-        EXPECT_TRUE(result) << result.Failure();
-        if (!result) {
+        EXPECT_EQ(result, Success);
+        if (result != Success) {
             return;
         }
 
         // Validate the output IR.
-        auto valid = ir::Validate(mod);
-        EXPECT_TRUE(valid) << valid.Failure().reason.str();
+        EXPECT_EQ(ir::Validate(mod), Success);
     }
 
     /// @returns the transformed module as a disassembled string
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 d9118c1..1d3e5ad 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture.cc
@@ -582,7 +582,7 @@
 
 Result<SuccessType> MultiplanarExternalTexture(Module& ir, const ExternalTextureOptions& options) {
     auto result = ValidateAndDumpIfNeeded(ir, "MultiplanarExternalTexture transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc b/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc
index 5e2336a..bdecc75 100644
--- a/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc
+++ b/src/tint/lang/core/ir/transform/multiplanar_external_texture_test.cc
@@ -344,7 +344,7 @@
       %b5 = block {  # false
         %24:vec4<f32> = textureLoad %plane_0, %coords_1, 0u
         %25:f32 = access %24, 0u
-        %26:vec2<u32> = shiftr %coords_1, vec2<u32>(1u)
+        %26:vec2<u32> = shr %coords_1, vec2<u32>(1u)
         %27:vec4<f32> = textureLoad %plane_1, %26, 0u
         %28:vec2<f32> = swizzle %27, xy
         %29:vec4<f32> = construct %25, %28, 1.0f
@@ -489,7 +489,7 @@
       %b5 = block {  # false
         %25:vec4<f32> = textureLoad %plane_0, %coords_1, 0u
         %26:f32 = access %25, 0u
-        %27:vec2<u32> = shiftr %coords_1, vec2<u32>(1u)
+        %27:vec2<u32> = shr %coords_1, vec2<u32>(1u)
         %28:vec4<f32> = textureLoad %plane_1, %27, 0u
         %29:vec2<f32> = swizzle %28, xy
         %30:vec4<f32> = construct %26, %29, 1.0f
@@ -1224,7 +1224,7 @@
       %b5 = block {  # false
         %38:vec4<f32> = textureLoad %plane_0, %coords_1, 0u
         %39:f32 = access %38, 0u
-        %40:vec2<u32> = shiftr %coords_1, vec2<u32>(1u)
+        %40:vec2<u32> = shr %coords_1, vec2<u32>(1u)
         %41:vec4<f32> = textureLoad %plane_1, %40, 0u
         %42:vec2<f32> = swizzle %41, xy
         %43:vec4<f32> = construct %39, %42, 1.0f
diff --git a/src/tint/lang/core/ir/transform/preserve_padding.cc b/src/tint/lang/core/ir/transform/preserve_padding.cc
index c9a87e8..49da61f 100644
--- a/src/tint/lang/core/ir/transform/preserve_padding.cc
+++ b/src/tint/lang/core/ir/transform/preserve_padding.cc
@@ -174,7 +174,7 @@
 
 Result<SuccessType> PreservePadding(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "PreservePadding transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/robustness.cc b/src/tint/lang/core/ir/transform/robustness.cc
index 25d9d1e..5791b9b 100644
--- a/src/tint/lang/core/ir/transform/robustness.cc
+++ b/src/tint/lang/core/ir/transform/robustness.cc
@@ -350,7 +350,7 @@
 
 Result<SuccessType> Robustness(Module& ir, const RobustnessConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "Robustness transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/shader_io.cc b/src/tint/lang/core/ir/transform/shader_io.cc
index ce24b54..a0c6d78 100644
--- a/src/tint/lang/core/ir/transform/shader_io.cc
+++ b/src/tint/lang/core/ir/transform/shader_io.cc
@@ -41,50 +41,6 @@
 
 namespace {
 
-core::BuiltinValue FunctionParamBuiltin(enum FunctionParam::Builtin builtin) {
-    switch (builtin) {
-        case FunctionParam::Builtin::kVertexIndex:
-            return core::BuiltinValue::kVertexIndex;
-        case FunctionParam::Builtin::kInstanceIndex:
-            return core::BuiltinValue::kInstanceIndex;
-        case FunctionParam::Builtin::kPosition:
-            return core::BuiltinValue::kPosition;
-        case FunctionParam::Builtin::kFrontFacing:
-            return core::BuiltinValue::kFrontFacing;
-        case FunctionParam::Builtin::kLocalInvocationId:
-            return core::BuiltinValue::kLocalInvocationId;
-        case FunctionParam::Builtin::kLocalInvocationIndex:
-            return core::BuiltinValue::kLocalInvocationIndex;
-        case FunctionParam::Builtin::kGlobalInvocationId:
-            return core::BuiltinValue::kGlobalInvocationId;
-        case FunctionParam::Builtin::kWorkgroupId:
-            return core::BuiltinValue::kWorkgroupId;
-        case FunctionParam::Builtin::kNumWorkgroups:
-            return core::BuiltinValue::kNumWorkgroups;
-        case FunctionParam::Builtin::kSampleIndex:
-            return core::BuiltinValue::kSampleIndex;
-        case FunctionParam::Builtin::kSampleMask:
-            return core::BuiltinValue::kSampleMask;
-        case FunctionParam::Builtin::kSubgroupInvocationId:
-            return core::BuiltinValue::kSubgroupInvocationId;
-        case FunctionParam::Builtin::kSubgroupSize:
-            return core::BuiltinValue::kSubgroupSize;
-    }
-    return core::BuiltinValue::kUndefined;
-}
-
-core::BuiltinValue ReturnBuiltin(enum Function::ReturnBuiltin builtin) {
-    switch (builtin) {
-        case Function::ReturnBuiltin::kPosition:
-            return core::BuiltinValue::kPosition;
-        case Function::ReturnBuiltin::kFragDepth:
-            return core::BuiltinValue::kFragDepth;
-        case Function::ReturnBuiltin::kSampleMask:
-            return core::BuiltinValue::kSampleMask;
-    }
-    return core::BuiltinValue::kUndefined;
-}
-
 /// PIMPL state for the transform.
 struct State {
     /// The IR module.
@@ -185,7 +141,7 @@
                     }
                     param->ClearLocation();
                 } else if (auto builtin = param->Builtin()) {
-                    attributes.builtin = FunctionParamBuiltin(*builtin);
+                    attributes.builtin = *builtin;
                     param->ClearBuiltin();
                 }
                 attributes.invariant = param->Invariant();
@@ -223,7 +179,7 @@
                 }
                 func->ClearReturnLocation();
             } else if (auto builtin = func->ReturnBuiltin()) {
-                attributes.builtin = ReturnBuiltin(*builtin);
+                attributes.builtin = *builtin;
                 func->ClearReturnBuiltin();
             }
             attributes.invariant = func->ReturnInvariant();
diff --git a/src/tint/lang/core/ir/transform/std140.cc b/src/tint/lang/core/ir/transform/std140.cc
index 85a2036..ba436eb 100644
--- a/src/tint/lang/core/ir/transform/std140.cc
+++ b/src/tint/lang/core/ir/transform/std140.cc
@@ -357,7 +357,7 @@
 
 Result<SuccessType> Std140(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "Std140 transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/value_to_let.cc b/src/tint/lang/core/ir/transform/value_to_let.cc
index 7580900..bbdcab0 100644
--- a/src/tint/lang/core/ir/transform/value_to_let.cc
+++ b/src/tint/lang/core/ir/transform/value_to_let.cc
@@ -174,7 +174,7 @@
 
 Result<SuccessType> ValueToLet(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "ValueToLet transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
index 134844c..a9e2de6 100644
--- a/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
+++ b/src/tint/lang/core/ir/transform/vectorize_scalar_matrix_constructors.cc
@@ -97,7 +97,7 @@
 
 Result<SuccessType> VectorizeScalarMatrixConstructors(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "VectorizeScalarMatrixConstructors transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
index 4bfa2cf..db902b4 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory.cc
@@ -285,7 +285,7 @@
             } else {
                 // Check if the parameter is the local invocation index.
                 if (param->Builtin() &&
-                    param->Builtin().value() == FunctionParam::Builtin::kLocalInvocationIndex) {
+                    param->Builtin().value() == BuiltinValue::kLocalInvocationIndex) {
                     return param;
                 }
             }
@@ -294,7 +294,7 @@
         // No local invocation index was found, so add one to the parameter list and use that.
         Vector<FunctionParam*, 4> params = func->Params();
         auto* param = b.FunctionParam("tint_local_index", ty.u32());
-        param->SetBuiltin(FunctionParam::Builtin::kLocalInvocationIndex);
+        param->SetBuiltin(BuiltinValue::kLocalInvocationIndex);
         params.Push(param);
         func->SetParams(params);
         return param;
@@ -368,7 +368,7 @@
 
 Result<SuccessType> ZeroInitWorkgroupMemory(Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "ZeroInitWorkgroupMemory transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
index d0a972e..37ba499 100644
--- a/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
+++ b/src/tint/lang/core/ir/transform/zero_init_workgroup_memory_test.cc
@@ -1434,9 +1434,9 @@
 
     auto* func = MakeEntryPoint("main", 1, 1, 1);
     auto* global_id = b.FunctionParam("global_id", ty.vec4<u32>());
-    global_id->SetBuiltin(FunctionParam::Builtin::kGlobalInvocationId);
+    global_id->SetBuiltin(BuiltinValue::kGlobalInvocationId);
     auto* index = b.FunctionParam("index", ty.u32());
-    index->SetBuiltin(FunctionParam::Builtin::kLocalInvocationIndex);
+    index->SetBuiltin(BuiltinValue::kLocalInvocationIndex);
     func->SetParams({global_id, index});
     b.Append(func->Block(), [&] {  //
         b.Load(var);
diff --git a/src/tint/lang/core/ir/validator.cc b/src/tint/lang/core/ir/validator.cc
index 9c24c34..fbd64d9 100644
--- a/src/tint/lang/core/ir/validator.cc
+++ b/src/tint/lang/core/ir/validator.cc
@@ -580,7 +580,8 @@
 
     auto result = core::intrinsic::LookupFn(context, call->FriendlyName().c_str(), call->FuncId(),
                                             args, core::EvaluationStage::kRuntime, Source{});
-    if (result) {
+    // TODO(bclayton): Error if result != Success
+    if (result == Success) {
         if (result->return_type != call->Result(0)->Type()) {
             AddError(call, InstError(call, "call result type does not match builtin return type"));
         }
@@ -932,7 +933,7 @@
 
 #ifndef NDEBUG
     auto result = Validate(ir);
-    if (!result) {
+    if (result != Success) {
         return result.Failure();
     }
 #endif
diff --git a/src/tint/lang/core/ir/validator_test.cc b/src/tint/lang/core/ir/validator_test.cc
index bb43fe3..7dd1d69 100644
--- a/src/tint/lang/core/ir/validator_test.cc
+++ b/src/tint/lang/core/ir/validator_test.cc
@@ -48,8 +48,7 @@
 
 TEST_F(IR_ValidatorTest, RootBlock_Var) {
     mod.root_block->Append(b.Var(ty.ptr<private_, i32>()));
-    auto res = ir::Validate(mod);
-    EXPECT_TRUE(res) << res.Failure().reason.str();
+    EXPECT_EQ(ir::Validate(mod), Success);
 }
 
 TEST_F(IR_ValidatorTest, RootBlock_NonVar) {
@@ -59,7 +58,7 @@
     mod.root_block->Append(l);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:2:3 error: root block: invalid instruction: tint::core::ir::Loop
   loop [b: %b2] {  # loop_1
@@ -90,7 +89,7 @@
     var->SetBlock(f->Block());
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:2:38 error: var: instruction in root block does not have root block as parent
   %1:ptr<private, i32, read_write> = var
@@ -119,8 +118,7 @@
     f->SetParams({b.FunctionParam(ty.i32()), b.FunctionParam(ty.f32())});
     f->Block()->Append(b.Return(f));
 
-    auto res = ir::Validate(mod);
-    EXPECT_TRUE(res) << res.Failure().reason.str();
+    EXPECT_EQ(ir::Validate(mod), Success);
 }
 
 TEST_F(IR_ValidatorTest, Function_Duplicate) {
@@ -132,7 +130,7 @@
     f->Block()->Append(b.Return(f));
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(error: function 'my_func' added to module multiple times
 note: # Disassembly
@@ -161,7 +159,7 @@
     b.Append(g->Block(), [&] { b.Return(g); });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:3:20 error: call: call target is not part of the module
     %2:void = call %g
@@ -185,7 +183,7 @@
     b.Function("my_func", ty.void_());
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:2:3 error: block: does not end in a terminator instruction
   %b1 = block {
@@ -212,7 +210,7 @@
     var->SetBlock(g->Block());
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:3:41 error: var: block instruction does not have same block as parent
     %2:ptr<function, i32, read_write> = var
@@ -252,7 +250,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:3:25 error: access: constant index must be positive, got -1
     %3:f32 = access %2, -1i
@@ -283,7 +281,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:3:29 error: access: index out of bounds for type vec2<f32>
     %3:f32 = access %2, 1u, 3u
@@ -318,7 +316,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:3:55 error: access: index out of bounds for type ptr<array<f32, 2>>
     %3:ptr<private, f32, read_write> = access %2, 1u, 3u
@@ -353,7 +351,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:25 error: access: type f32 cannot be indexed
     %3:f32 = access %2, 1u
                         ^^
@@ -383,7 +381,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:51 error: access: type ptr<f32> cannot be indexed
     %3:ptr<private, f32, read_write> = access %2, 1u
                                                   ^^
@@ -419,7 +417,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:8:25 error: access: type MyStruct cannot be dynamically indexed
     %4:i32 = access %2, %3
@@ -461,7 +459,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:8:25 error: access: type ptr<MyStruct> cannot be dynamically indexed
     %4:i32 = access %2, %3
@@ -497,7 +495,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:3:14 error: access: result of access chain is type f32 but instruction type is i32
     %3:i32 = access %2, 1u, 1u
@@ -528,7 +526,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(
         res.Failure().reason.str(),
         R"(:3:40 error: access: result of access chain is type ptr<f32> but instruction type is ptr<i32>
@@ -560,7 +558,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(
         res.Failure().reason.str(),
         R"(:3:14 error: access: result of access chain is type ptr<f32> but instruction type is f32
@@ -592,7 +590,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:3:25 error: access: cannot obtain address of vector element
     %3:f32 = access %2, 1u
@@ -623,7 +621,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:3:29 error: access: cannot obtain address of vector element
     %3:f32 = access %2, 1u, 1u
@@ -654,7 +652,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_TRUE(res) << res.Failure().reason.str();
+    ASSERT_EQ(res, Success);
 }
 
 TEST_F(IR_ValidatorTest, Access_IndexVector_ViaMatrix) {
@@ -668,7 +666,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_TRUE(res) << res.Failure().reason.str();
+    ASSERT_EQ(res, Success);
 }
 
 TEST_F(IR_ValidatorTest, Block_TerminatorInMiddle) {
@@ -680,7 +678,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:3:5 error: block: terminator which isn't the final instruction
     ret
@@ -709,8 +707,7 @@
     f->Block()->Append(if_);
     f->Block()->Append(b.Return(f));
 
-    auto res = ir::Validate(mod);
-    EXPECT_TRUE(res) << res.Failure().reason.str();
+    EXPECT_EQ(ir::Validate(mod), Success);
 }
 
 TEST_F(IR_ValidatorTest, If_EmptyTrue) {
@@ -723,7 +720,7 @@
     f->Block()->Append(b.Return(f));
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:4:7 error: block: does not end in a terminator instruction
       %b2 = block {  # true
@@ -756,7 +753,7 @@
     f->Block()->Append(b.Return(f));
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:8 error: if: condition must be a `bool` type
     if 1i [t: %b2, f: %b3] {  # if_1
        ^^
@@ -793,7 +790,7 @@
     f->Block()->Append(b.Return(f));
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:8 error: if: operand is undefined
     if undef [t: %b2, f: %b3] {  # if_1
        ^^^^^
@@ -832,7 +829,7 @@
     f->Block()->Append(b.Return(f));
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:5 error: if: result is undefined
     undef = if true [t: %b2, f: %b3] {  # if_1
     ^^^^^
@@ -868,8 +865,7 @@
     sb.Append(l);
     sb.Return(f);
 
-    auto res = ir::Validate(mod);
-    EXPECT_TRUE(res) << res.Failure().reason.str();
+    EXPECT_EQ(ir::Validate(mod), Success);
 }
 
 TEST_F(IR_ValidatorTest, Loop_EmptyBody) {
@@ -880,7 +876,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:4:7 error: block: does not end in a terminator instruction
       %b2 = block {  # body
@@ -904,7 +900,7 @@
     mod.root_block->Append(v);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:2:3 error: var: result is undefined
   undef = var
   ^^^^^
@@ -931,7 +927,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:5 error: var: result is undefined
     undef = var
     ^^^^^
@@ -961,7 +957,7 @@
     v->SetInitializer(result);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:41 error: var: initializer has incorrect type
     %2:ptr<function, f32, read_write> = var, %3
                                         ^^^
@@ -990,7 +986,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:5 error: let: result is undefined
     undef = let 1i
     ^^^^^
@@ -1019,7 +1015,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:18 error: let: operand is undefined
     %2:f32 = let undef
                  ^^^^^
@@ -1048,7 +1044,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:14 error: let: result type does not match value type
     %2:f32 = let 1i
              ^^^
@@ -1101,7 +1097,7 @@
     expected = tint::ReplaceAll(expected, "$ARROWS", arrows);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), expected);
 }
 
@@ -1115,7 +1111,7 @@
     v->Result(0)->SetInstruction(nullptr);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:3:5 error: var: instruction of result is undefined
     %2:ptr<function, f32, read_write> = var
@@ -1147,7 +1143,7 @@
     v->SetInitializer(result);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:46 error: var: operand is not alive
     %2:ptr<function, f32, read_write> = var, %3
                                              ^^
@@ -1178,7 +1174,7 @@
     result->RemoveUsage({v, 0u});
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:46 error: var: operand missing usage
     %2:ptr<function, f32, read_write> = var, %3
                                              ^^
@@ -1208,7 +1204,7 @@
     load->Remove();
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(error: orphaned instruction: load
 note: # Disassembly
 %my_func = func():void -> %b1 {
@@ -1228,7 +1224,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:18 error: binary: operand is undefined
     %2:i32 = add undef, 2i
                  ^^^^^
@@ -1255,7 +1251,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:22 error: binary: operand is undefined
     %2:i32 = add 2i, undef
                      ^^^^^
@@ -1285,7 +1281,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:5 error: binary: result is undefined
     undef = add 3i, 2i
     ^^^^^
@@ -1312,7 +1308,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:23 error: unary: operand is undefined
     %2:i32 = negation undef
                       ^^^^^
@@ -1342,7 +1338,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:5 error: unary: result is undefined
     undef = negation 2i
     ^^^^^
@@ -1371,7 +1367,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:5 error: unary: result type must match value type
     %2:f32 = complement 2i
     ^^^^^^^^^^^^^^^^^^^^^^
@@ -1399,8 +1395,7 @@
     sb.Append(if_);
     sb.Return(f);
 
-    auto res = ir::Validate(mod);
-    EXPECT_TRUE(res) << res.Failure().reason.str();
+    EXPECT_EQ(ir::Validate(mod), Success);
 }
 
 TEST_F(IR_ValidatorTest, ExitIf_NullIf) {
@@ -1413,7 +1408,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:5:9 error: exit_if: has no parent control instruction
         exit_if  # undef
         ^^^^^^^
@@ -1450,7 +1445,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(
         res.Failure().reason.str(),
         R"(:5:9 error: exit_if: args count (1) does not match control instruction result count (2)
@@ -1494,7 +1489,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(
         res.Failure().reason.str(),
         R"(:5:9 error: exit_if: args count (3) does not match control instruction result count (2)
@@ -1538,7 +1533,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_TRUE(res) << res.Failure().reason.str();
+    ASSERT_EQ(res, Success);
 }
 
 TEST_F(IR_ValidatorTest, ExitIf_IncorrectResultType) {
@@ -1555,7 +1550,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(
         res.Failure().reason.str(),
         R"(:5:21 error: exit_if: argument type (f32) does not match control instruction type (i32)
@@ -1596,7 +1591,7 @@
     sb.ExitIf(if_);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:8:5 error: exit_if: found outside all control instructions
     exit_if  # if_1
@@ -1639,7 +1634,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:7:13 error: exit_if: if target jumps over other control instructions
             exit_if  # if_1
@@ -1692,7 +1687,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:7:13 error: exit_if: if target jumps over other control instructions
             exit_if  # if_1
@@ -1744,7 +1739,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:7:13 error: exit_if: if target jumps over other control instructions
             exit_if  # if_1
@@ -1788,8 +1783,7 @@
     sb.Append(switch_);
     sb.Return(f);
 
-    auto res = ir::Validate(mod);
-    EXPECT_TRUE(res) << res.Failure().reason.str();
+    EXPECT_EQ(ir::Validate(mod), Success);
 }
 
 TEST_F(IR_ValidatorTest, ExitSwitch_NullSwitch) {
@@ -1804,7 +1798,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:5:9 error: exit_switch: has no parent control instruction
         exit_switch  # undef
@@ -1844,7 +1838,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(
         res.Failure().reason.str(),
         R"(:5:9 error: exit_switch: args count (1) does not match control instruction result count (2)
@@ -1888,7 +1882,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(
         res.Failure().reason.str(),
         R"(:5:9 error: exit_switch: args count (3) does not match control instruction result count (2)
@@ -1932,7 +1926,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_TRUE(res) << res.Failure().reason.str();
+    ASSERT_EQ(res, Success);
 }
 
 TEST_F(IR_ValidatorTest, ExitSwitch_IncorrectResultType) {
@@ -1950,7 +1944,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(
         res.Failure().reason.str(),
         R"(:5:25 error: exit_switch: argument type (f32) does not match control instruction type (i32)
@@ -1995,7 +1989,7 @@
     sb.Append(b.Return(f));
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:10:9 error: exit_switch: switch not found in parent control instructions
         exit_switch  # switch_1
@@ -2053,8 +2047,7 @@
     sb.Append(switch_);
     sb.Return(f);
 
-    auto res = ir::Validate(mod);
-    EXPECT_TRUE(res) << res.Failure().reason.str();
+    EXPECT_EQ(ir::Validate(mod), Success);
 }
 
 TEST_F(IR_ValidatorTest, ExitSwitch_InvalidJumpOverSwitch) {
@@ -2077,7 +2070,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:7:13 error: exit_switch: switch target jumps over other control instructions
             exit_switch  # switch_1
@@ -2128,7 +2121,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:7:13 error: exit_switch: switch target jumps over other control instructions
             exit_switch  # switch_1
@@ -2171,8 +2164,7 @@
     sb.Append(loop);
     sb.Return(f);
 
-    auto res = ir::Validate(mod);
-    EXPECT_TRUE(res) << res.Failure().reason.str();
+    EXPECT_EQ(ir::Validate(mod), Success);
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_NullLoop) {
@@ -2186,7 +2178,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:5:9 error: exit_loop: has no parent control instruction
         exit_loop  # undef
@@ -2228,7 +2220,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(
         res.Failure().reason.str(),
         R"(:5:9 error: exit_loop: args count (1) does not match control instruction result count (2)
@@ -2275,7 +2267,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(
         res.Failure().reason.str(),
         R"(:5:9 error: exit_loop: args count (3) does not match control instruction result count (2)
@@ -2322,7 +2314,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_TRUE(res) << res.Failure().reason.str();
+    ASSERT_EQ(res, Success);
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_IncorrectResultType) {
@@ -2340,7 +2332,7 @@
     sb.Return(f);
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(
         res.Failure().reason.str(),
         R"(:5:23 error: exit_loop: argument type (f32) does not match control instruction type (i32)
@@ -2387,7 +2379,7 @@
     sb.Append(b.Return(f));
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:13:9 error: exit_loop: loop not found in parent control instructions
         exit_loop  # loop_1
@@ -2447,8 +2439,7 @@
     sb.Append(loop);
     sb.Return(f);
 
-    auto res = ir::Validate(mod);
-    EXPECT_TRUE(res) << res.Failure().reason.str();
+    EXPECT_EQ(ir::Validate(mod), Success);
 }
 
 TEST_F(IR_ValidatorTest, ExitLoop_InvalidJumpOverSwitch) {
@@ -2471,7 +2462,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:7:13 error: exit_loop: loop target jumps over other control instructions
             exit_loop  # loop_1
@@ -2526,7 +2517,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:7:13 error: exit_loop: loop target jumps over other control instructions
             exit_loop  # loop_1
@@ -2576,7 +2567,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:8:9 error: exit_loop: loop exit jumps out of continuing block
         exit_loop  # loop_1
@@ -2622,7 +2613,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:10:13 error: exit_loop: loop exit jumps out of continuing block
             exit_loop  # loop_1
@@ -2674,7 +2665,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:5:9 error: exit_loop: loop exit not permitted in loop initializer
         exit_loop  # loop_1
@@ -2724,7 +2715,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:7:13 error: exit_loop: loop exit not permitted in loop initializer
             exit_loop  # loop_1
@@ -2769,8 +2760,7 @@
         b.Return(f);
     });
 
-    auto res = ir::Validate(mod);
-    EXPECT_TRUE(res) << res.Failure().reason.str();
+    EXPECT_EQ(ir::Validate(mod), Success);
 }
 
 TEST_F(IR_ValidatorTest, Return_WithValue) {
@@ -2779,8 +2769,7 @@
         b.Return(f, 42_i);
     });
 
-    auto res = ir::Validate(mod);
-    EXPECT_TRUE(res) << res.Failure().reason.str();
+    EXPECT_EQ(ir::Validate(mod), Success);
 }
 
 TEST_F(IR_ValidatorTest, Return_NullFunction) {
@@ -2790,7 +2779,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:5 error: return: undefined function
     ret
     ^^^
@@ -2815,7 +2804,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:5 error: return: unexpected return value
     ret 42i
     ^^^^^^^
@@ -2840,7 +2829,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:5 error: return: expected return value
     ret
     ^^^
@@ -2865,7 +2854,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:3:5 error: return: return value type does not match function return type
     ret 42.0f
@@ -2893,7 +2882,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:11 error: store: operand is undefined
     store undef, 42i
           ^^^^^
@@ -2922,7 +2911,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:4:15 error: store: operand is undefined
     store %2, undef
               ^^^^^
@@ -2952,7 +2941,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:4:15 error: value type does not match pointer element type
     store %2, 42u
@@ -2984,7 +2973,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(),
               R"(:4:5 error: load_vector_element: result is undefined
     undef = load_vector_element %2, 1i
@@ -3015,7 +3004,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:34 error: load_vector_element: operand is undefined
     %2:f32 = load_vector_element undef, 1i
                                  ^^^^^
@@ -3045,7 +3034,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:4:38 error: load_vector_element: operand is undefined
     %3:f32 = load_vector_element %2, undef
                                      ^^^^^
@@ -3075,7 +3064,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:3:26 error: store_vector_element: operand is undefined
     store_vector_element undef, 1i, 2i
                          ^^^^^
@@ -3105,7 +3094,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:4:30 error: store_vector_element: operand is undefined
     store_vector_element %2, undef, 2i
                              ^^^^^
@@ -3144,7 +3133,7 @@
     });
 
     auto res = ir::Validate(mod);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(:4:34 error: store_vector_element: operand is undefined
     store_vector_element %2, 1i, undef
                                  ^^^^^
diff --git a/src/tint/lang/core/type/BUILD.bazel b/src/tint/lang/core/type/BUILD.bazel
index f813fdc..e3c6b72 100644
--- a/src/tint/lang/core/type/BUILD.bazel
+++ b/src/tint/lang/core/type/BUILD.bazel
@@ -55,6 +55,7 @@
     "i32.cc",
     "manager.cc",
     "matrix.cc",
+    "memory_view.cc",
     "multisampled_texture.cc",
     "node.cc",
     "numeric_scalar.cc",
@@ -92,6 +93,7 @@
     "i32.h",
     "manager.h",
     "matrix.h",
+    "memory_view.h",
     "multisampled_texture.h",
     "node.h",
     "numeric_scalar.h",
diff --git a/src/tint/lang/core/type/BUILD.cmake b/src/tint/lang/core/type/BUILD.cmake
index 273cb07..d212503 100644
--- a/src/tint/lang/core/type/BUILD.cmake
+++ b/src/tint/lang/core/type/BUILD.cmake
@@ -72,6 +72,8 @@
   lang/core/type/manager.h
   lang/core/type/matrix.cc
   lang/core/type/matrix.h
+  lang/core/type/memory_view.cc
+  lang/core/type/memory_view.h
   lang/core/type/multisampled_texture.cc
   lang/core/type/multisampled_texture.h
   lang/core/type/node.cc
diff --git a/src/tint/lang/core/type/BUILD.gn b/src/tint/lang/core/type/BUILD.gn
index 7da2908..8942f2a 100644
--- a/src/tint/lang/core/type/BUILD.gn
+++ b/src/tint/lang/core/type/BUILD.gn
@@ -77,6 +77,8 @@
     "manager.h",
     "matrix.cc",
     "matrix.h",
+    "memory_view.cc",
+    "memory_view.h",
     "multisampled_texture.cc",
     "multisampled_texture.h",
     "node.cc",
diff --git a/src/tint/lang/core/type/manager.cc b/src/tint/lang/core/type/manager.cc
index 50c1a39..8c9b297 100644
--- a/src/tint/lang/core/type/manager.cc
+++ b/src/tint/lang/core/type/manager.cc
@@ -174,16 +174,19 @@
 
 const core::type::Array* Manager::runtime_array(const core::type::Type* elem_ty,
                                                 uint32_t stride /* = 0 */) {
+    uint32_t implicit_stride = tint::RoundUp(elem_ty->Align(), elem_ty->Size());
     if (stride == 0) {
-        stride = elem_ty->Align();
+        stride = implicit_stride;
     }
+    TINT_ASSERT(stride >= implicit_stride);
+
     return Get<core::type::Array>(
         /* element type */ elem_ty,
         /* element count */ Get<RuntimeArrayCount>(),
         /* array alignment */ elem_ty->Align(),
         /* array size */ stride,
         /* element stride */ stride,
-        /* implicit stride */ elem_ty->Align());
+        /* implicit stride */ implicit_stride);
 }
 
 const core::type::Pointer* Manager::ptr(core::AddressSpace address_space,
diff --git a/src/tint/lang/core/type/manager_test.cc b/src/tint/lang/core/type/manager_test.cc
index 81bcdaa..cd141fb 100644
--- a/src/tint/lang/core/type/manager_test.cc
+++ b/src/tint/lang/core/type/manager_test.cc
@@ -28,15 +28,19 @@
 #include "src/tint/lang/core/type/manager.h"
 
 #include "gtest/gtest.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/matrix.h"
 #include "src/tint/lang/core/type/u32.h"
 
 namespace tint::core::type {
 namespace {
 
+using namespace tint::core::fluent_types;  // NOLINT
+
 template <typename T>
 size_t count(const T& range_loopable) {
     size_t n = 0;
@@ -124,5 +128,19 @@
     EXPECT_EQ(count(outer), 1u);
 }
 
+TEST_F(ManagerTest, ArrayImplicitStride) {
+    Manager tm;
+    auto* arr = tm.array<mat4x4<f32>, 4>();
+    EXPECT_EQ(arr->Stride(), 64u);
+    EXPECT_EQ(arr->ImplicitStride(), 64u);
+}
+
+TEST_F(ManagerTest, RuntimeSizedArrayImplicitStride) {
+    Manager tm;
+    auto* arr = tm.array<mat4x4<f32>>();
+    EXPECT_EQ(arr->Stride(), 64u);
+    EXPECT_EQ(arr->ImplicitStride(), 64u);
+}
+
 }  // namespace
 }  // namespace tint::core::type
diff --git a/src/tint/lang/wgsl/writer/ast_printer/ast_printer_fuzz.cc b/src/tint/lang/core/type/memory_view.cc
similarity index 67%
copy from src/tint/lang/wgsl/writer/ast_printer/ast_printer_fuzz.cc
copy to src/tint/lang/core/type/memory_view.cc
index 230ab20..6ea0d41 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/ast_printer_fuzz.cc
+++ b/src/tint/lang/core/type/memory_view.cc
@@ -1,4 +1,4 @@
-// Copyright 2021 The Dawn & Tint Authors
+// 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:
@@ -25,20 +25,26 @@
 // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
-// GEN_BUILD:CONDITION(tint_build_wgsl_reader)
+#include "src/tint/lang/core/type/memory_view.h"
 
-#include "src/tint/lang/wgsl/writer/ast_printer/ast_printer.h"
+#include "src/tint/lang/core/type/manager.h"
+#include "src/tint/utils/diagnostic/diagnostic.h"
 
-#include "src/tint/cmd/fuzz/wgsl/fuzz.h"
+TINT_INSTANTIATE_TYPEINFO(tint::core::type::MemoryView);
 
-namespace tint::wgsl::writer {
-namespace {
+namespace tint::core::type {
 
-void ASTPrinterFuzzer(const tint::Program& program) {
-    ASTPrinter{program}.Generate();
+MemoryView::MemoryView(size_t hash,
+                       core::AddressSpace address_space,
+                       const Type* store_type,
+                       core::Access access)
+    : Base(hash, core::type::Flags{}),
+      store_type_(store_type),
+      address_space_(address_space),
+      access_(access) {
+    TINT_ASSERT(access != core::Access::kUndefined);
 }
 
-}  // namespace
-}  // namespace tint::wgsl::writer
+MemoryView::~MemoryView() = default;
 
-TINT_WGSL_PROGRAM_FUZZER(tint::wgsl::writer::ASTPrinterFuzzer);
+}  // namespace tint::core::type
diff --git a/src/tint/lang/core/type/memory_view.h b/src/tint/lang/core/type/memory_view.h
new file mode 100644
index 0000000..de14277
--- /dev/null
+++ b/src/tint/lang/core/type/memory_view.h
@@ -0,0 +1,72 @@
+// 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_TYPE_MEMORY_VIEW_H_
+#define SRC_TINT_LANG_CORE_TYPE_MEMORY_VIEW_H_
+
+#include <string>
+
+#include "src/tint/lang/core/access.h"
+#include "src/tint/lang/core/address_space.h"
+#include "src/tint/lang/core/type/type.h"
+
+namespace tint::core::type {
+
+/// Base class for memory view types: pointers and references
+class MemoryView : public Castable<MemoryView, Type> {
+  public:
+    /// Constructor
+    /// @param hash the unique hash of the node
+    /// @param address_space the address space of the memory view
+    /// @param store_type the store type
+    /// @param access the resolved access control of the reference
+    MemoryView(size_t hash,
+               core::AddressSpace address_space,
+               const Type* store_type,
+               core::Access access);
+
+    /// Destructor
+    ~MemoryView() override;
+
+    /// @returns the store type of the memory view
+    const Type* StoreType() const { return store_type_; }
+
+    /// @returns the address space of the memory view
+    core::AddressSpace AddressSpace() const { return address_space_; }
+
+    /// @returns the access control of the memory view
+    core::Access Access() const { return access_; }
+
+  private:
+    Type const* const store_type_;
+    core::AddressSpace const address_space_;
+    core::Access const access_;
+};
+
+}  // namespace tint::core::type
+
+#endif  // SRC_TINT_LANG_CORE_TYPE_MEMORY_VIEW_H_
diff --git a/src/tint/lang/core/type/pointer.cc b/src/tint/lang/core/type/pointer.cc
index 12af588..bb59afe 100644
--- a/src/tint/lang/core/type/pointer.cc
+++ b/src/tint/lang/core/type/pointer.cc
@@ -30,7 +30,6 @@
 #include "src/tint/lang/core/type/manager.h"
 #include "src/tint/lang/core/type/reference.h"
 #include "src/tint/utils/diagnostic/diagnostic.h"
-#include "src/tint/utils/ice/ice.h"
 #include "src/tint/utils/math/hash.h"
 #include "src/tint/utils/text/string_stream.h"
 
@@ -38,20 +37,13 @@
 
 namespace tint::core::type {
 
-Pointer::Pointer(core::AddressSpace address_space, const Type* subtype, core::Access access)
-    : Base(Hash(tint::TypeInfo::Of<Pointer>().full_hashcode, address_space, subtype, access),
-           core::type::Flags{}),
-      subtype_(subtype),
-      address_space_(address_space),
-      access_(access) {
-    TINT_ASSERT(!subtype->Is<Reference>());
-    TINT_ASSERT(access != core::Access::kUndefined);
-}
+Pointer::Pointer(core::AddressSpace address_space, const Type* store_type, core::Access access)
+    : Base(Hash(tint::TypeInfo::Of<Pointer>().full_hashcode), address_space, store_type, access) {}
 
 bool Pointer::Equals(const UniqueNode& other) const {
     if (auto* o = other.As<Pointer>()) {
-        return o->address_space_ == address_space_ && o->subtype_ == subtype_ &&
-               o->access_ == access_;
+        return o->AddressSpace() == AddressSpace() && o->StoreType() == StoreType() &&
+               o->Access() == Access();
     }
     return false;
 }
@@ -59,10 +51,10 @@
 std::string Pointer::FriendlyName() const {
     StringStream out;
     out << "ptr<";
-    if (address_space_ != core::AddressSpace::kUndefined) {
-        out << address_space_ << ", ";
+    if (AddressSpace() != core::AddressSpace::kUndefined) {
+        out << AddressSpace() << ", ";
     }
-    out << subtype_->FriendlyName() << ", " << access_;
+    out << StoreType()->FriendlyName() << ", " << Access();
     out << ">";
     return out.str();
 }
@@ -70,8 +62,8 @@
 Pointer::~Pointer() = default;
 
 Pointer* Pointer::Clone(CloneContext& ctx) const {
-    auto* ty = subtype_->Clone(ctx);
-    return ctx.dst.mgr->Get<Pointer>(address_space_, ty, access_);
+    auto* ty = StoreType()->Clone(ctx);
+    return ctx.dst.mgr->Get<Pointer>(AddressSpace(), ty, Access());
 }
 
 }  // namespace tint::core::type
diff --git a/src/tint/lang/core/type/pointer.h b/src/tint/lang/core/type/pointer.h
index 0f27b0a..50bbfd7 100644
--- a/src/tint/lang/core/type/pointer.h
+++ b/src/tint/lang/core/type/pointer.h
@@ -30,20 +30,17 @@
 
 #include <string>
 
-#include "src/tint/lang/core/access.h"
-#include "src/tint/lang/core/address_space.h"
-#include "src/tint/lang/core/type/type.h"
-
+#include "src/tint/lang/core/type/memory_view.h"
 namespace tint::core::type {
 
 /// A pointer type.
-class Pointer final : public Castable<Pointer, Type> {
+class Pointer final : public Castable<Pointer, MemoryView> {
   public:
     /// Constructor
     /// @param address_space the address space of the pointer
-    /// @param subtype the pointee type
+    /// @param store_type the pointee type
     /// @param access the resolved access control of the reference
-    Pointer(core::AddressSpace address_space, const Type* subtype, core::Access access);
+    Pointer(core::AddressSpace address_space, const Type* store_type, core::Access access);
 
     /// Destructor
     ~Pointer() override;
@@ -52,15 +49,6 @@
     /// @returns true if the this type is equal to @p other
     bool Equals(const UniqueNode& other) const override;
 
-    /// @returns the pointee type
-    const Type* StoreType() const { return subtype_; }
-
-    /// @returns the address space of the pointer
-    core::AddressSpace AddressSpace() const { return address_space_; }
-
-    /// @returns the access control of the reference
-    core::Access Access() const { return access_; }
-
     /// @returns the name for this type that closely resembles how it would be
     /// declared in WGSL.
     std::string FriendlyName() const override;
@@ -68,11 +56,6 @@
     /// @param ctx the clone context
     /// @returns a clone of this type
     Pointer* Clone(CloneContext& ctx) const override;
-
-  private:
-    Type const* const subtype_;
-    core::AddressSpace const address_space_;
-    core::Access const access_;
 };
 
 }  // namespace tint::core::type
diff --git a/src/tint/lang/core/type/reference.cc b/src/tint/lang/core/type/reference.cc
index 7db8d8a..230ca87 100644
--- a/src/tint/lang/core/type/reference.cc
+++ b/src/tint/lang/core/type/reference.cc
@@ -29,7 +29,6 @@
 
 #include "src/tint/lang/core/type/manager.h"
 #include "src/tint/utils/diagnostic/diagnostic.h"
-#include "src/tint/utils/ice/ice.h"
 #include "src/tint/utils/math/hash.h"
 #include "src/tint/utils/text/string_stream.h"
 
@@ -37,20 +36,13 @@
 
 namespace tint::core::type {
 
-Reference::Reference(core::AddressSpace address_space, const Type* subtype, core::Access access)
-    : Base(Hash(tint::TypeInfo::Of<Reference>().full_hashcode, address_space, subtype, access),
-           core::type::Flags{}),
-      subtype_(subtype),
-      address_space_(address_space),
-      access_(access) {
-    TINT_ASSERT(!subtype->Is<Reference>());
-    TINT_ASSERT(access != core::Access::kUndefined);
-}
+Reference::Reference(core::AddressSpace address_space, const Type* store_type, core::Access access)
+    : Base(Hash(tint::TypeInfo::Of<Pointer>().full_hashcode), address_space, store_type, access) {}
 
 bool Reference::Equals(const UniqueNode& other) const {
     if (auto* o = other.As<Reference>()) {
-        return o->address_space_ == address_space_ && o->subtype_ == subtype_ &&
-               o->access_ == access_;
+        return o->AddressSpace() == AddressSpace() && o->StoreType() == StoreType() &&
+               o->Access() == Access();
     }
     return false;
 }
@@ -58,10 +50,10 @@
 std::string Reference::FriendlyName() const {
     StringStream out;
     out << "ref<";
-    if (address_space_ != core::AddressSpace::kUndefined) {
-        out << address_space_ << ", ";
+    if (AddressSpace() != core::AddressSpace::kUndefined) {
+        out << AddressSpace() << ", ";
     }
-    out << subtype_->FriendlyName() << ", " << access_;
+    out << StoreType()->FriendlyName() << ", " << Access();
     out << ">";
     return out.str();
 }
@@ -69,8 +61,8 @@
 Reference::~Reference() = default;
 
 Reference* Reference::Clone(CloneContext& ctx) const {
-    auto* ty = subtype_->Clone(ctx);
-    return ctx.dst.mgr->Get<Reference>(address_space_, ty, access_);
+    auto* ty = StoreType()->Clone(ctx);
+    return ctx.dst.mgr->Get<Reference>(AddressSpace(), ty, Access());
 }
 
 }  // namespace tint::core::type
diff --git a/src/tint/lang/core/type/reference.h b/src/tint/lang/core/type/reference.h
index 38843aa..b74593c 100644
--- a/src/tint/lang/core/type/reference.h
+++ b/src/tint/lang/core/type/reference.h
@@ -30,20 +30,18 @@
 
 #include <string>
 
-#include "src/tint/lang/core/access.h"
-#include "src/tint/lang/core/address_space.h"
-#include "src/tint/lang/core/type/type.h"
+#include "src/tint/lang/core/type/memory_view.h"
 
 namespace tint::core::type {
 
 /// A reference type.
-class Reference final : public Castable<Reference, Type> {
+class Reference final : public Castable<Reference, MemoryView> {
   public:
     /// Constructor
     /// @param address_space the address space of the reference
-    /// @param subtype the pointee type
+    /// @param store_type the store type
     /// @param access the resolved access control of the reference
-    Reference(core::AddressSpace address_space, const Type* subtype, core::Access access);
+    Reference(core::AddressSpace address_space, const Type* store_type, core::Access access);
 
     /// Destructor
     ~Reference() override;
@@ -52,15 +50,6 @@
     /// @returns true if the this type is equal to @p other
     bool Equals(const UniqueNode& other) const override;
 
-    /// @returns the pointee type
-    const Type* StoreType() const { return subtype_; }
-
-    /// @returns the address space of the reference
-    core::AddressSpace AddressSpace() const { return address_space_; }
-
-    /// @returns the resolved access control of the reference.
-    core::Access Access() const { return access_; }
-
     /// @returns the name for this type that closely resembles how it would be
     /// declared in WGSL.
     std::string FriendlyName() const override;
@@ -68,11 +57,6 @@
     /// @param ctx the clone context
     /// @returns a clone of this type
     Reference* Clone(CloneContext& ctx) const override;
-
-  private:
-    Type const* const subtype_;
-    core::AddressSpace const address_space_;
-    core::Access const access_;
 };
 
 }  // namespace tint::core::type
diff --git a/src/tint/lang/core/type/type.cc b/src/tint/lang/core/type/type.cc
index ce95883..8be0c71 100644
--- a/src/tint/lang/core/type/type.cc
+++ b/src/tint/lang/core/type/type.cc
@@ -72,6 +72,14 @@
     return type;
 }
 
+const Type* Type::UnwrapPtrOrRef() const {
+    auto* type = UnwrapPtr();
+    if (type != this) {
+        return type;
+    }
+    return UnwrapRef();
+}
+
 uint32_t Type::Size() const {
     return 0;
 }
diff --git a/src/tint/lang/core/type/type.h b/src/tint/lang/core/type/type.h
index 61723cd..fae0c44 100644
--- a/src/tint/lang/core/type/type.h
+++ b/src/tint/lang/core/type/type.h
@@ -96,6 +96,9 @@
     /// @returns the inner type if this is a reference, `this` otherwise
     const Type* UnwrapRef() const;
 
+    /// @returns the inner type if this is a pointer or a reference, `this` otherwise
+    const Type* UnwrapPtrOrRef() const;
+
     /// @returns the size in bytes of the type. This may include tail padding.
     /// @note opaque types will return a size of 0.
     virtual uint32_t Size() const;
diff --git a/src/tint/lang/glsl/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/glsl/writer/ast_printer/ast_printer_test.cc
index 4962165..19aac5b 100644
--- a/src/tint/lang/glsl/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/glsl/writer/ast_printer/ast_printer_test.cc
@@ -40,7 +40,7 @@
     auto program = resolver::Resolve(*this);
     ASSERT_FALSE(program.IsValid());
     auto result = Generate(program, Options{}, "");
-    EXPECT_FALSE(result);
+    EXPECT_NE(result, Success);
     EXPECT_EQ(result.Failure().reason.str(), "error: make the program invalid");
 }
 
diff --git a/src/tint/lang/glsl/writer/printer/helper_test.h b/src/tint/lang/glsl/writer/printer/helper_test.h
index 8a420e6..f6b31dd 100644
--- a/src/tint/lang/glsl/writer/printer/helper_test.h
+++ b/src/tint/lang/glsl/writer/printer/helper_test.h
@@ -62,13 +62,13 @@
     /// Run the writer on the IR module and validate the result.
     /// @returns true if generation and validation succeeded
     bool Generate() {
-        if (auto raised = raise::Raise(mod); !raised) {
+        if (auto raised = raise::Raise(mod); raised != Success) {
             err_ = raised.Failure().reason.str();
             return false;
         }
 
         auto result = Print(mod, version);
-        if (!result) {
+        if (result != Success) {
             err_ = result.Failure().reason.str();
             return false;
         }
diff --git a/src/tint/lang/glsl/writer/printer/printer.cc b/src/tint/lang/glsl/writer/printer/printer.cc
index 790931d..dfee449 100644
--- a/src/tint/lang/glsl/writer/printer/printer.cc
+++ b/src/tint/lang/glsl/writer/printer/printer.cc
@@ -56,7 +56,7 @@
     /// @returns the generated GLSL shader
     tint::Result<std::string> Generate(const Version& version) {
         auto valid = core::ir::ValidateAndDumpIfNeeded(ir_, "GLSL writer");
-        if (!valid) {
+        if (valid != Success) {
             return std::move(valid.Failure());
         }
 
diff --git a/src/tint/lang/glsl/writer/writer.cc b/src/tint/lang/glsl/writer/writer.cc
index 7283deb..1402afe 100644
--- a/src/tint/lang/glsl/writer/writer.cc
+++ b/src/tint/lang/glsl/writer/writer.cc
@@ -40,13 +40,13 @@
     Output output;
 
     // Raise from core-dialect to GLSL-dialect.
-    if (auto res = raise::Raise(ir); !res) {
+    if (auto res = raise::Raise(ir); res != Success) {
         return res.Failure();
     }
 
     // Generate the GLSL code.
     auto result = Print(ir, options.version);
-    if (!result) {
+    if (result != Success) {
         return result.Failure();
     }
     output.glsl = result.Get();
diff --git a/src/tint/lang/glsl/writer/writer_bench.cc b/src/tint/lang/glsl/writer/writer_bench.cc
index 42278ee..162e5b0 100644
--- a/src/tint/lang/glsl/writer/writer_bench.cc
+++ b/src/tint/lang/glsl/writer/writer_bench.cc
@@ -37,7 +37,7 @@
 
 void GenerateGLSL(benchmark::State& state, std::string input_name) {
     auto res = bench::LoadProgram(input_name);
-    if (!res) {
+    if (res != Success) {
         state.SkipWithError(res.Failure().reason.str());
         return;
     }
@@ -52,7 +52,7 @@
     for (auto _ : state) {
         for (auto& ep : entry_points) {
             auto gen_res = Generate(program, {}, ep);
-            if (!gen_res) {
+            if (gen_res != Success) {
                 state.SkipWithError(gen_res.Failure().reason.str());
             }
         }
diff --git a/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc
index 632907a..0d68b42 100644
--- a/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/hlsl/writer/ast_printer/ast_printer_test.cc
@@ -40,7 +40,7 @@
     auto program = resolver::Resolve(*this);
     ASSERT_FALSE(program.IsValid());
     auto result = Generate(program, Options{});
-    EXPECT_FALSE(result);
+    EXPECT_NE(result, Success);
     EXPECT_EQ(result.Failure().reason.str(), "error: make the program invalid");
 }
 
diff --git a/src/tint/lang/hlsl/writer/writer_bench.cc b/src/tint/lang/hlsl/writer/writer_bench.cc
index c5b8a73..4e9a856 100644
--- a/src/tint/lang/hlsl/writer/writer_bench.cc
+++ b/src/tint/lang/hlsl/writer/writer_bench.cc
@@ -35,13 +35,13 @@
 
 void GenerateHLSL(benchmark::State& state, std::string input_name) {
     auto res = bench::LoadProgram(input_name);
-    if (!res) {
+    if (res != Success) {
         state.SkipWithError(res.Failure().reason.str());
         return;
     }
     for (auto _ : state) {
         auto gen_res = Generate(res->program, {});
-        if (!gen_res) {
+        if (gen_res != Success) {
             state.SkipWithError(gen_res.Failure().reason.str());
         }
     }
diff --git a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
index 4f90706..517885a 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer.cc
@@ -3034,8 +3034,28 @@
 void ASTPrinter::EmitLoopPreserver() {
     IncrementIndent();
     // This statement prevents the MSL compiler from erasing a loop during
-    // optimimzations.
+    // optimizations.  In the AIR dialiect of LLVM IR, WGSL loops should compile
+    // to a loop that contains an 'asm' call with a 'sideeffect' annotation.
+    //
+    // For example, compile a WGSL file with a trivial while(1) loop to 'a.metal',
+    // then compile that to AIR (LLVM IR dialect):
+    //
+    //    xcrun metal a.metal -S -o -
+    //
+    // The loop in the AIR should look something like this:
+    //
+    //    1: ...
+    //      br label %2
+    //
+    //    2:                                      ; preds = %1, %2
+    //      tail call void asm sideeffect "", ""() #1, !srcloc !27
+    //      br label %2, !llvm.loop !28
+    //
+    // It is important that the 'sideeffect' annotation exist. That tells the
+    // optimizer that the instruction has side effects invisible to the
+    // optimizer, and therefore the loop should not be eliminated.
     Line() << R"(__asm__("");)";
+
     DecrementIndent();
 }
 
diff --git a/src/tint/lang/msl/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/msl/writer/ast_printer/ast_printer_test.cc
index f7b869e..0477e48 100644
--- a/src/tint/lang/msl/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/msl/writer/ast_printer/ast_printer_test.cc
@@ -44,7 +44,7 @@
     auto program = resolver::Resolve(*this);
     ASSERT_FALSE(program.IsValid());
     auto result = Generate(program, Options{});
-    EXPECT_FALSE(result);
+    EXPECT_NE(result, Success);
     EXPECT_EQ(result.Failure().reason.str(), "error: make the program invalid");
 }
 
diff --git a/src/tint/lang/msl/writer/common/option_helpers.cc b/src/tint/lang/msl/writer/common/option_helpers.cc
index cda70c6..2561e9f 100644
--- a/src/tint/lang/msl/writer/common/option_helpers.cc
+++ b/src/tint/lang/msl/writer/common/option_helpers.cc
@@ -153,7 +153,8 @@
 }
 
 // The remapped binding data and external texture data need to coordinate in order to put things in
-// the correct place when we're done.
+// the correct place when we're done. The binding remapper is run first, so make sure that the
+// external texture uses the new binding point.
 //
 // When the data comes in we have a list of all WGSL origin (group,binding) pairs to MSL
 // (binding) in the `uniform`, `storage`, `texture`, and `sampler` arrays.
@@ -193,9 +194,10 @@
         BindingPoint plane1_binding_point{0, plane1.binding};
         BindingPoint metadata_binding_point{0, metadata.binding};
 
-        // Use the re-bound msl plane0 value for the lookup key.
+        // Use the re-bound MSL plane0 value for the lookup key. The group goes to `0` which is the
+        // value always used for re-bound data.
         external_texture.bindings_map.emplace(
-            BindingPoint{src_binding_point.group, plane0_binding_point.binding},
+            BindingPoint{0, plane0_binding_point.binding},
             ExternalTextureOptions::BindingPoints{plane1_binding_point, metadata_binding_point});
 
         // Bindings which go to the same slot in MSL do not need to be re-bound.
diff --git a/src/tint/lang/msl/writer/printer/helper_test.h b/src/tint/lang/msl/writer/printer/helper_test.h
index bf14aa6..b52e60d 100644
--- a/src/tint/lang/msl/writer/printer/helper_test.h
+++ b/src/tint/lang/msl/writer/printer/helper_test.h
@@ -80,13 +80,13 @@
     /// Run the writer on the IR module and validate the result.
     /// @returns true if generation and validation succeeded
     bool Generate() {
-        if (auto raised = raise::Raise(mod, {}); !raised) {
+        if (auto raised = raise::Raise(mod, {}); raised != Success) {
             err_ = raised.Failure().reason.str();
             return false;
         }
 
         auto result = Print(mod);
-        if (!result) {
+        if (result != Success) {
             err_ = result.Failure().reason.str();
             return false;
         }
diff --git a/src/tint/lang/msl/writer/printer/printer.cc b/src/tint/lang/msl/writer/printer/printer.cc
index 24b4b29..6cfa838 100644
--- a/src/tint/lang/msl/writer/printer/printer.cc
+++ b/src/tint/lang/msl/writer/printer/printer.cc
@@ -107,7 +107,7 @@
     /// @returns the generated MSL shader
     tint::Result<std::string> Generate() {
         auto valid = core::ir::ValidateAndDumpIfNeeded(ir_, "MSL writer");
-        if (!valid) {
+        if (valid != Success) {
             return std::move(valid.Failure());
         }
 
@@ -270,31 +270,31 @@
                 if (param->Builtin().has_value()) {
                     out << " [[";
                     switch (param->Builtin().value()) {
-                        case core::ir::FunctionParam::Builtin::kFrontFacing:
+                        case core::BuiltinValue::kFrontFacing:
                             out << "front_facing";
                             break;
-                        case core::ir::FunctionParam::Builtin::kGlobalInvocationId:
+                        case core::BuiltinValue::kGlobalInvocationId:
                             out << "thread_position_in_grid";
                             break;
-                        case core::ir::FunctionParam::Builtin::kLocalInvocationId:
+                        case core::BuiltinValue::kLocalInvocationId:
                             out << "thread_position_in_threadgroup";
                             break;
-                        case core::ir::FunctionParam::Builtin::kLocalInvocationIndex:
+                        case core::BuiltinValue::kLocalInvocationIndex:
                             out << "thread_index_in_threadgroup";
                             break;
-                        case core::ir::FunctionParam::Builtin::kNumWorkgroups:
+                        case core::BuiltinValue::kNumWorkgroups:
                             out << "threadgroups_per_grid";
                             break;
-                        case core::ir::FunctionParam::Builtin::kPosition:
+                        case core::BuiltinValue::kPosition:
                             out << "position";
                             break;
-                        case core::ir::FunctionParam::Builtin::kSampleIndex:
+                        case core::BuiltinValue::kSampleIndex:
                             out << "sample_id";
                             break;
-                        case core::ir::FunctionParam::Builtin::kSampleMask:
+                        case core::BuiltinValue::kSampleMask:
                             out << "sample_mask";
                             break;
-                        case core::ir::FunctionParam::Builtin::kWorkgroupId:
+                        case core::BuiltinValue::kWorkgroupId:
                             out << "threadgroup_position_in_grid";
                             break;
 
diff --git a/src/tint/lang/msl/writer/raise/builtin_polyfill.cc b/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
index 1905246..ab0da1d 100644
--- a/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/msl/writer/raise/builtin_polyfill.cc
@@ -145,7 +145,7 @@
 
 Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "BuiltinPolyfill transform");
-    if (!result) {
+    if (result != Success) {
         return result.Failure();
     }
 
diff --git a/src/tint/lang/msl/writer/raise/raise.cc b/src/tint/lang/msl/writer/raise/raise.cc
index 4140f46..2d970cc 100644
--- a/src/tint/lang/msl/writer/raise/raise.cc
+++ b/src/tint/lang/msl/writer/raise/raise.cc
@@ -49,7 +49,7 @@
 #define RUN_TRANSFORM(name, ...)                   \
     do {                                           \
         auto result = name(module, ##__VA_ARGS__); \
-        if (!result) {                             \
+        if (result != Success) {                   \
             return result;                         \
         }                                          \
     } while (false)
diff --git a/src/tint/lang/msl/writer/writer.cc b/src/tint/lang/msl/writer/writer.cc
index bc1a5e0..e997959 100644
--- a/src/tint/lang/msl/writer/writer.cc
+++ b/src/tint/lang/msl/writer/writer.cc
@@ -45,7 +45,7 @@
 Result<Output> Generate(core::ir::Module& ir, const Options& options) {
     {
         auto res = ValidateBindingOptions(options);
-        if (!res) {
+        if (res != Success) {
             return res.Failure();
         }
     }
@@ -53,13 +53,13 @@
     Output output;
 
     // Raise from core-dialect to MSL-dialect.
-    if (auto res = raise::Raise(ir, options); !res) {
+    if (auto res = raise::Raise(ir, options); res != Success) {
         return res.Failure();
     }
 
     // Generate the MSL code.
     auto result = Print(ir);
-    if (!result) {
+    if (result != Success) {
         return result.Failure();
     }
     output.msl = result.Get();
@@ -73,7 +73,7 @@
 
     {
         auto res = ValidateBindingOptions(options);
-        if (!res) {
+        if (res != Success) {
             return res.Failure();
         }
     }
diff --git a/src/tint/lang/msl/writer/writer_bench.cc b/src/tint/lang/msl/writer/writer_bench.cc
index b89b8f7..d597dbd 100644
--- a/src/tint/lang/msl/writer/writer_bench.cc
+++ b/src/tint/lang/msl/writer/writer_bench.cc
@@ -38,7 +38,7 @@
 
 void GenerateMSL(benchmark::State& state, std::string input_name) {
     auto res = bench::LoadProgram(input_name);
-    if (!res) {
+    if (res != Success) {
         state.SkipWithError(res.Failure().reason.str());
         return;
     }
@@ -66,7 +66,7 @@
 
     for (auto _ : state) {
         auto gen_res = Generate(program, gen_options);
-        if (!gen_res) {
+        if (gen_res != Success) {
             state.SkipWithError(gen_res.Failure().reason.str());
         }
     }
diff --git a/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc b/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
index 9370817..e8b5aeb 100644
--- a/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
+++ b/src/tint/lang/spirv/reader/ast_parser/ast_parser.cc
@@ -2040,7 +2040,7 @@
                                        ast::IntLiteralExpression::Suffix::kU)};
         },
         [&](const F32*) {
-            if (auto f = core::CheckedConvert<f32>(AFloat(spirv_const->GetFloat()))) {
+            if (auto f = core::CheckedConvert<f32>(AFloat(spirv_const->GetFloat())); f == Success) {
                 return TypedExpression{ty_.F32(),
                                        create<ast::FloatLiteralExpression>(
                                            source, static_cast<double>(spirv_const->GetFloat()),
diff --git a/src/tint/lang/spirv/reader/parser/helper_test.h b/src/tint/lang/spirv/reader/parser/helper_test.h
index 7b2b547..af16699 100644
--- a/src/tint/lang/spirv/reader/parser/helper_test.h
+++ b/src/tint/lang/spirv/reader/parser/helper_test.h
@@ -73,13 +73,13 @@
 
         // Parse the SPIR-V to produce an IR module.
         auto parsed = Parse(Slice(binary.data(), binary.size()));
-        if (!parsed) {
+        if (parsed != Success) {
             return parsed.Failure().reason.str();
         }
 
         // Validate the IR module.
         auto validated = core::ir::Validate(parsed.Get());
-        if (!validated) {
+        if (validated != Success) {
             return validated.Failure().reason.str();
         }
 
diff --git a/src/tint/lang/spirv/reader/parser/parser.cc b/src/tint/lang/spirv/reader/parser/parser.cc
index 20cc942..d7a8ef1 100644
--- a/src/tint/lang/spirv/reader/parser/parser.cc
+++ b/src/tint/lang/spirv/reader/parser/parser.cc
@@ -63,7 +63,7 @@
     Result<core::ir::Module> Run(Slice<const uint32_t> spirv) {
         // Validate the incoming SPIR-V binary.
         auto result = validate::Validate(spirv, kTargetEnv);
-        if (!result) {
+        if (result != Success) {
             return result.Failure();
         }
 
diff --git a/src/tint/lang/spirv/reader/reader.cc b/src/tint/lang/spirv/reader/reader.cc
index 1930b97..11199ea 100644
--- a/src/tint/lang/spirv/reader/reader.cc
+++ b/src/tint/lang/spirv/reader/reader.cc
@@ -37,7 +37,7 @@
 
 Result<core::ir::Module> ReadIR(const std::vector<uint32_t>& input) {
     auto mod = Parse(Slice(input.data(), input.size()));
-    if (!mod) {
+    if (mod != Success) {
         return mod.Failure();
     }
 
diff --git a/src/tint/lang/spirv/validate/validate_test.cc b/src/tint/lang/spirv/validate/validate_test.cc
index 14dde40..270f065 100644
--- a/src/tint/lang/spirv/validate/validate_test.cc
+++ b/src/tint/lang/spirv/validate/validate_test.cc
@@ -49,7 +49,7 @@
         0x00050051, 0x00000005, 0x00000010, 0x0000000f, 0x00000001, 0x000100fd, 0x00010038,
     };
     auto res = Validate(spirv, SPV_ENV_VULKAN_1_3);
-    EXPECT_TRUE(res) << res;
+    EXPECT_EQ(res, Success);
 }
 
 TEST(SpirvValidateTest, Invalid) {
@@ -69,7 +69,7 @@
         0x00050051, 0x00000005, 0x00000010, 0x0000000f, 0x00000001, 0x000100fd, 0x00010038,
     };
     auto res = Validate(spirv, SPV_ENV_VULKAN_1_3);
-    ASSERT_FALSE(res);
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(spirv error: SPIR-V failed validation.
 
 Disassembly:
diff --git a/src/tint/lang/spirv/writer/ast_printer/ast_printer_test.cc b/src/tint/lang/spirv/writer/ast_printer/ast_printer_test.cc
index 5cbe8d4..bf096b9 100644
--- a/src/tint/lang/spirv/writer/ast_printer/ast_printer_test.cc
+++ b/src/tint/lang/spirv/writer/ast_printer/ast_printer_test.cc
@@ -39,7 +39,7 @@
     auto program = resolver::Resolve(*this);
     ASSERT_FALSE(program.IsValid());
     auto result = Generate(program, Options{});
-    EXPECT_FALSE(result);
+    EXPECT_NE(result, Success);
     EXPECT_EQ(result.Failure().reason.str(), "error: make the program invalid");
 }
 
@@ -48,7 +48,7 @@
 
     auto program = resolver::Resolve(*this);
     auto result = Generate(program, Options{});
-    EXPECT_FALSE(result);
+    EXPECT_NE(result, Success);
     EXPECT_EQ(
         result.Failure().reason.str(),
         R"(12:34 error: SPIR-V backend does not support extension 'chromium_internal_relaxed_uniform_layout')");
@@ -59,7 +59,7 @@
 
     auto program = resolver::Resolve(*this);
     auto result = Generate(program, Options{});
-    EXPECT_TRUE(result);
+    EXPECT_EQ(result, Success);
 }
 
 }  // namespace
diff --git a/src/tint/lang/spirv/writer/ast_writer_fuzz.cc b/src/tint/lang/spirv/writer/ast_writer_fuzz.cc
index c70990c..f63cca6 100644
--- a/src/tint/lang/spirv/writer/ast_writer_fuzz.cc
+++ b/src/tint/lang/spirv/writer/ast_writer_fuzz.cc
@@ -43,12 +43,12 @@
         return;
     }
     auto output = Generate(no_overrides, options);
-    if (!output) {
+    if (output != Success) {
         return;
     }
     auto& spirv = output->spirv;
     if (auto res = validate::Validate(Slice(spirv.data(), spirv.size()), SPV_ENV_VULKAN_1_1);
-        !res) {
+        res != Success) {
         TINT_ICE() << "Output of SPIR-V writer failed to validate with SPIR-V Tools\n"
                    << res.Failure();
     }
diff --git a/src/tint/lang/spirv/writer/common/helper_test.h b/src/tint/lang/spirv/writer/common/helper_test.h
index f04c95c..df4936e 100644
--- a/src/tint/lang/spirv/writer/common/helper_test.h
+++ b/src/tint/lang/spirv/writer/common/helper_test.h
@@ -116,13 +116,13 @@
     /// @returns true if generation and validation succeeded
     bool Generate(Options options = {}, bool zero_init_workgroup_memory = false) {
         auto raised = raise::Raise(mod, options);
-        if (!raised) {
+        if (raised != Success) {
             err_ = raised.Failure().reason.str();
             return false;
         }
 
         auto spirv = PrintModule(mod, zero_init_workgroup_memory);
-        if (!spirv) {
+        if (spirv != Success) {
             err_ = spirv.Failure().reason.str();
             return false;
         }
diff --git a/src/tint/lang/spirv/writer/discard_test.cc b/src/tint/lang/spirv/writer/discard_test.cc
index 5e9ccf9..843ffa6 100644
--- a/src/tint/lang/spirv/writer/discard_test.cc
+++ b/src/tint/lang/spirv/writer/discard_test.cc
@@ -39,7 +39,7 @@
     mod.root_block->Append(buffer);
 
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(core::ir::FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(core::BuiltinValue::kFrontFacing);
     auto* ep = b.Function("ep", ty.f32(), core::ir::Function::PipelineStage::kFragment);
     ep->SetParams({front_facing});
     ep->SetReturnLocation(0_u, {});
@@ -91,7 +91,7 @@
     mod.root_block->Append(buffer);
 
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(core::ir::FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(core::BuiltinValue::kFrontFacing);
     auto* ep = b.Function("ep", ty.f32(), core::ir::Function::PipelineStage::kFragment);
     ep->SetParams({front_facing});
     ep->SetReturnLocation(0_u, {});
diff --git a/src/tint/lang/spirv/writer/function_test.cc b/src/tint/lang/spirv/writer/function_test.cc
index 85904d4..2658f3f 100644
--- a/src/tint/lang/spirv/writer/function_test.cc
+++ b/src/tint/lang/spirv/writer/function_test.cc
@@ -315,7 +315,7 @@
 
 TEST_F(SpirvWriterTest, Function_ShaderIO_VertexPointSize) {
     auto* func = b.Function("main", ty.vec4<f32>(), core::ir::Function::PipelineStage::kVertex);
-    func->SetReturnBuiltin(core::ir::Function::ReturnBuiltin::kPosition);
+    func->SetReturnBuiltin(core::BuiltinValue::kPosition);
     b.Append(func->Block(), [&] {  //
         b.Return(func, b.Construct(ty.vec4<f32>(), 0.5_f));
     });
diff --git a/src/tint/lang/spirv/writer/printer/printer.cc b/src/tint/lang/spirv/writer/printer/printer.cc
index add607e..e5e9945 100644
--- a/src/tint/lang/spirv/writer/printer/printer.cc
+++ b/src/tint/lang/spirv/writer/printer/printer.cc
@@ -189,7 +189,7 @@
 
     /// @returns the generated SPIR-V code on success, or failure
     Result<std::vector<uint32_t>> Code() {
-        if (auto res = Generate(); !res) {
+        if (auto res = Generate(); res != Success) {
             return res.Failure();
         }
 
@@ -202,7 +202,7 @@
 
     /// @returns the generated SPIR-V module on success, or failure
     Result<writer::Module> Module() {
-        if (auto res = Generate(); !res) {
+        if (auto res = Generate(); res != Success) {
             return res.Failure();
         }
 
@@ -292,7 +292,7 @@
     /// Builds the SPIR-V from the IR
     Result<SuccessType> Generate() {
         auto valid = core::ir::ValidateAndDumpIfNeeded(ir_, "SPIR-V writer");
-        if (!valid) {
+        if (valid != Success) {
             return valid.Failure();
         }
 
diff --git a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
index 923a526..335b3b7 100644
--- a/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
+++ b/src/tint/lang/spirv/writer/raise/builtin_polyfill.cc
@@ -888,7 +888,7 @@
 
 Result<SuccessType> BuiltinPolyfill(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "BuiltinPolyfill transform");
-    if (!result) {
+    if (result != Success) {
         return result.Failure();
     }
 
diff --git a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
index 033cb2a..083a98b 100644
--- a/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
+++ b/src/tint/lang/spirv/writer/raise/expand_implicit_splats.cc
@@ -146,7 +146,7 @@
 
 Result<SuccessType> ExpandImplicitSplats(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "ExpandImplicitSplats transform");
-    if (!result) {
+    if (result != Success) {
         return result.Failure();
     }
 
diff --git a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
index 43ede7c..6be5daf 100644
--- a/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
+++ b/src/tint/lang/spirv/writer/raise/handle_matrix_arithmetic.cc
@@ -168,7 +168,7 @@
 
 Result<SuccessType> HandleMatrixArithmetic(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "HandleMatrixArithmetic transform");
-    if (!result) {
+    if (result != Success) {
         return result.Failure();
     }
 
diff --git a/src/tint/lang/spirv/writer/raise/merge_return.cc b/src/tint/lang/spirv/writer/raise/merge_return.cc
index cab47ce..41bafb9 100644
--- a/src/tint/lang/spirv/writer/raise/merge_return.cc
+++ b/src/tint/lang/spirv/writer/raise/merge_return.cc
@@ -301,7 +301,7 @@
 
 Result<SuccessType> MergeReturn(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "MergeReturn transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/spirv/writer/raise/pass_matrix_by_pointer.cc b/src/tint/lang/spirv/writer/raise/pass_matrix_by_pointer.cc
index 2fe2e74..52b2968 100644
--- a/src/tint/lang/spirv/writer/raise/pass_matrix_by_pointer.cc
+++ b/src/tint/lang/spirv/writer/raise/pass_matrix_by_pointer.cc
@@ -129,7 +129,7 @@
 
 Result<SuccessType> PassMatrixByPointer(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "PassMatrixByPointer transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/spirv/writer/raise/raise.cc b/src/tint/lang/spirv/writer/raise/raise.cc
index 153bd2c..1c88637 100644
--- a/src/tint/lang/spirv/writer/raise/raise.cc
+++ b/src/tint/lang/spirv/writer/raise/raise.cc
@@ -60,7 +60,7 @@
 #define RUN_TRANSFORM(name, ...)         \
     do {                                 \
         auto result = name(__VA_ARGS__); \
-        if (!result) {                   \
+        if (result != Success) {         \
             return result;               \
         }                                \
     } while (false)
diff --git a/src/tint/lang/spirv/writer/raise/shader_io.cc b/src/tint/lang/spirv/writer/raise/shader_io.cc
index 5ec25d1..b4a8be9 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io.cc
@@ -223,7 +223,7 @@
 
 Result<SuccessType> ShaderIO(core::ir::Module& ir, const ShaderIOConfig& config) {
     auto result = ValidateAndDumpIfNeeded(ir, "ShaderIO transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/spirv/writer/raise/shader_io_test.cc b/src/tint/lang/spirv/writer/raise/shader_io_test.cc
index bfea71b..0b5a962 100644
--- a/src/tint/lang/spirv/writer/raise/shader_io_test.cc
+++ b/src/tint/lang/spirv/writer/raise/shader_io_test.cc
@@ -68,9 +68,9 @@
 TEST_F(SpirvWriter_ShaderIOTest, Parameters_NonStruct) {
     auto* ep = b.Function("foo", ty.void_());
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(core::ir::FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(core::BuiltinValue::kFrontFacing);
     auto* position = b.FunctionParam("position", ty.vec4<f32>());
-    position->SetBuiltin(core::ir::FunctionParam::Builtin::kPosition);
+    position->SetBuiltin(core::BuiltinValue::kPosition);
     position->SetInvariant(true);
     auto* color1 = b.FunctionParam("color1", ty.f32());
     color1->SetLocation(0, {});
@@ -328,7 +328,7 @@
 
     auto* ep = b.Function("foo", ty.void_());
     auto* front_facing = b.FunctionParam("front_facing", ty.bool_());
-    front_facing->SetBuiltin(core::ir::FunctionParam::Builtin::kFrontFacing);
+    front_facing->SetBuiltin(core::BuiltinValue::kFrontFacing);
     auto* str_param = b.FunctionParam("inputs", str_ty);
     auto* color2 = b.FunctionParam("color2", ty.f32());
     color2->SetLocation(1, core::Interpolation{core::InterpolationType::kLinear,
@@ -420,7 +420,7 @@
 
 TEST_F(SpirvWriter_ShaderIOTest, ReturnValue_NonStructBuiltin) {
     auto* ep = b.Function("foo", ty.vec4<f32>());
-    ep->SetReturnBuiltin(core::ir::Function::ReturnBuiltin::kPosition);
+    ep->SetReturnBuiltin(core::BuiltinValue::kPosition);
     ep->SetReturnInvariant(true);
     ep->SetStage(core::ir::Function::PipelineStage::kVertex);
 
@@ -975,7 +975,7 @@
                              });
 
     auto* mask_in = b.FunctionParam("mask_in", ty.u32());
-    mask_in->SetBuiltin(core::ir::FunctionParam::Builtin::kSampleMask);
+    mask_in->SetBuiltin(core::BuiltinValue::kSampleMask);
 
     auto* ep = b.Function("foo", str_ty);
     ep->SetStage(core::ir::Function::PipelineStage::kFragment);
@@ -1065,7 +1065,7 @@
     // Vertex shader.
     {
         auto* ep = b.Function("vert", ty.vec4<f32>());
-        ep->SetReturnBuiltin(core::ir::Function::ReturnBuiltin::kPosition);
+        ep->SetReturnBuiltin(core::BuiltinValue::kPosition);
         ep->SetReturnInvariant(true);
         ep->SetStage(core::ir::Function::PipelineStage::kVertex);
 
@@ -1446,7 +1446,7 @@
 TEST_F(SpirvWriter_ShaderIOTest, EmitVertexPointSize) {
     auto* ep = b.Function("foo", ty.vec4<f32>());
     ep->SetStage(core::ir::Function::PipelineStage::kVertex);
-    ep->SetReturnBuiltin(core::ir::Function::ReturnBuiltin::kPosition);
+    ep->SetReturnBuiltin(core::BuiltinValue::kPosition);
 
     b.Append(ep->Block(), [&] {  //
         b.Return(ep, b.Construct(ty.vec4<f32>(), 0.5_f));
diff --git a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
index 855ba42..32f0d1b 100644
--- a/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
+++ b/src/tint/lang/spirv/writer/raise/var_for_dynamic_index.cc
@@ -205,7 +205,7 @@
 
 Result<SuccessType> VarForDynamicIndex(core::ir::Module& ir) {
     auto result = ValidateAndDumpIfNeeded(ir, "VarForDynamicIndex transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/spirv/writer/writer.cc b/src/tint/lang/spirv/writer/writer.cc
index 1ee7d51..a605c36 100644
--- a/src/tint/lang/spirv/writer/writer.cc
+++ b/src/tint/lang/spirv/writer/writer.cc
@@ -46,7 +46,7 @@
 
     {
         auto res = ValidateBindingOptions(options);
-        if (!res) {
+        if (res != Success) {
             return res.Failure();
         }
     }
@@ -54,13 +54,13 @@
     Output output;
 
     // Raise from core-dialect to SPIR-V-dialect.
-    if (auto res = raise::Raise(ir, options); !res) {
+    if (auto res = raise::Raise(ir, options); res != Success) {
         return std::move(res.Failure());
     }
 
     // Generate the SPIR-V code.
     auto spirv = Print(ir, zero_initialize_workgroup_memory);
-    if (!spirv) {
+    if (spirv != Success) {
         return std::move(spirv.Failure());
     }
     output.spirv = std::move(spirv.Get());
@@ -78,7 +78,7 @@
 
     {
         auto res = ValidateBindingOptions(options);
-        if (!res) {
+        if (res != Success) {
             return res.Failure();
         }
     }
diff --git a/src/tint/lang/spirv/writer/writer_bench.cc b/src/tint/lang/spirv/writer/writer_bench.cc
index 2112631..84beb72 100644
--- a/src/tint/lang/spirv/writer/writer_bench.cc
+++ b/src/tint/lang/spirv/writer/writer_bench.cc
@@ -39,13 +39,13 @@
 
 void GenerateSPIRV(benchmark::State& state, std::string input_name) {
     auto res = bench::LoadProgram(input_name);
-    if (!res) {
+    if (res != Success) {
         state.SkipWithError(res.Failure().reason.str());
         return;
     }
     for (auto _ : state) {
         auto gen_res = Generate(res->program, {});
-        if (!gen_res) {
+        if (gen_res != Success) {
             state.SkipWithError(gen_res.Failure().reason.str());
         }
     }
@@ -54,20 +54,20 @@
 void GenerateSPIRV_UseIR(benchmark::State& state, std::string input_name) {
 #if TINT_BUILD_WGSL_READER
     auto res = bench::LoadProgram(input_name);
-    if (!res) {
+    if (res != Success) {
         state.SkipWithError(res.Failure().reason.str());
         return;
     }
     for (auto _ : state) {
         // Convert the AST program to an IR module.
         auto ir = tint::wgsl::reader::ProgramToLoweredIR(res->program);
-        if (!ir) {
+        if (ir != Success) {
             state.SkipWithError(ir.Failure().reason.str());
             return;
         }
 
         auto gen_res = Generate(ir.Get(), {});
-        if (!gen_res) {
+        if (gen_res != Success) {
             state.SkipWithError(gen_res.Failure().reason.str());
         }
     }
diff --git a/src/tint/lang/spirv/writer/writer_fuzz.cc b/src/tint/lang/spirv/writer/writer_fuzz.cc
index 5fdb191..421db7b 100644
--- a/src/tint/lang/spirv/writer/writer_fuzz.cc
+++ b/src/tint/lang/spirv/writer/writer_fuzz.cc
@@ -37,12 +37,12 @@
 void IRPrinterFuzzer(core::ir::Module& module, Options options) {
     options.bindings = GenerateBindings(module);
     auto output = Generate(module, options);
-    if (!output) {
+    if (output != Success) {
         return;
     }
     auto& spirv = output->spirv;
     if (auto res = validate::Validate(Slice(spirv.data(), spirv.size()), SPV_ENV_VULKAN_1_1);
-        !res) {
+        res != Success) {
         TINT_ICE() << "Output of SPIR-V writer failed to validate with SPIR-V Tools\n"
                    << res.Failure();
     }
diff --git a/src/tint/lang/wgsl/ast/module_clone_test.cc b/src/tint/lang/wgsl/ast/module_clone_test.cc
index 57386ce..9eeeb32 100644
--- a/src/tint/lang/wgsl/ast/module_clone_test.cc
+++ b/src/tint/lang/wgsl/ast/module_clone_test.cc
@@ -173,7 +173,7 @@
     std::string src_wgsl;
     {
         auto result = wgsl::writer::Generate(src, options);
-        ASSERT_TRUE(result) << result.Failure();
+        ASSERT_EQ(result, Success);
         src_wgsl = result->wgsl;
 
         // Move the src program to a temporary that'll be dropped, so that the src
@@ -186,7 +186,7 @@
 
     // Print the dst module, check it matches the original source
     auto result = wgsl::writer::Generate(dst, options);
-    ASSERT_TRUE(result);
+    ASSERT_EQ(result, Success);
     auto dst_wgsl = result->wgsl;
     ASSERT_EQ(src_wgsl, dst_wgsl);
 }
diff --git a/src/tint/lang/wgsl/ast/transform/helper_test.h b/src/tint/lang/wgsl/ast/transform/helper_test.h
index b7e5121..495f822 100644
--- a/src/tint/lang/wgsl/ast/transform/helper_test.h
+++ b/src/tint/lang/wgsl/ast/transform/helper_test.h
@@ -51,7 +51,7 @@
 
     wgsl::writer::Options options;
     auto result = wgsl::writer::Generate(program, options);
-    if (!result) {
+    if (result != Success) {
         return result.Failure().reason.str();
     }
 
diff --git a/src/tint/lang/wgsl/helpers/ir_program_test.h b/src/tint/lang/wgsl/helpers/ir_program_test.h
index 360bb06..6a71008 100644
--- a/src/tint/lang/wgsl/helpers/ir_program_test.h
+++ b/src/tint/lang/wgsl/helpers/ir_program_test.h
@@ -63,16 +63,16 @@
         }
 
         auto result = wgsl::reader::ProgramToIR(program);
-        if (!result) {
+        if (result != Success) {
             return result.Failure();
         }
 
         // WGSL-dialect -> core-dialect
-        if (auto lower = wgsl::reader::Lower(result.Get()); !lower) {
+        if (auto lower = wgsl::reader::Lower(result.Get()); lower != Success) {
             return lower.Failure();
         }
 
-        if (auto validate = core::ir::Validate(result.Get()); !validate) {
+        if (auto validate = core::ir::Validate(result.Get()); validate != Success) {
             return validate.Failure();
         }
         return result;
@@ -84,9 +84,9 @@
     Result<core::ir::Module> Build(std::string wgsl) {
         Source::File file("test.wgsl", std::move(wgsl));
         auto result = wgsl::reader::WgslToIR(&file);
-        if (result) {
+        if (result == Success) {
             auto validated = core::ir::Validate(result.Get());
-            if (!validated) {
+            if (validated != Success) {
                 return validated.Failure();
             }
         }
diff --git a/src/tint/lang/wgsl/ir_roundtrip_fuzz.cc b/src/tint/lang/wgsl/ir_roundtrip_fuzz.cc
index 12d97c1..b745f56 100644
--- a/src/tint/lang/wgsl/ir_roundtrip_fuzz.cc
+++ b/src/tint/lang/wgsl/ir_roundtrip_fuzz.cc
@@ -41,7 +41,7 @@
 namespace tint::wgsl {
 
 void IRRoundtripFuzzer(core::ir::Module& ir) {
-    if (auto res = tint::wgsl::writer::Raise(ir); !res) {
+    if (auto res = tint::wgsl::writer::Raise(ir); res != Success) {
         TINT_ICE() << res.Failure();
         return;
     }
@@ -49,7 +49,7 @@
     auto dst = tint::wgsl::writer::IRToProgram(ir);
     if (!dst.IsValid()) {
         std::cerr << "IR:\n" << core::ir::Disassemble(ir) << std::endl;
-        if (auto result = tint::wgsl::writer::Generate(dst, {}); result) {
+        if (auto result = tint::wgsl::writer::Generate(dst, {}); result == Success) {
             std::cerr << "WGSL:\n" << result->wgsl << std::endl << std::endl;
         }
         TINT_ICE() << dst.Diagnostics();
diff --git a/src/tint/lang/wgsl/ir_roundtrip_test.cc b/src/tint/lang/wgsl/ir_roundtrip_test.cc
index 8e430d3..ab1b3d6 100644
--- a/src/tint/lang/wgsl/ir_roundtrip_test.cc
+++ b/src/tint/lang/wgsl/ir_roundtrip_test.cc
@@ -47,14 +47,14 @@
         auto input = tint::TrimSpace(input_wgsl);
         Source::File file("test.wgsl", std::string(input));
         auto ir_module = wgsl::reader::WgslToIR(&file, options);
-        ASSERT_TRUE(ir_module) << ir_module;
+        ASSERT_EQ(ir_module, Success);
 
         auto disassembly = tint::core::ir::Disassemble(ir_module.Get());
 
         writer::ProgramOptions program_options;
         program_options.allowed_features = AllowedFeatures::Everything();
         auto output = wgsl::writer::WgslFromIR(ir_module.Get(), program_options);
-        if (!output) {
+        if (output != Success) {
             FAIL() << output.Failure() << std::endl  //
                    << "IR:" << std::endl             //
                    << disassembly << std::endl;
diff --git a/src/tint/lang/wgsl/program/clone_context_fuzz.cc b/src/tint/lang/wgsl/program/clone_context_fuzz.cc
index 17d802a..8aa341a 100644
--- a/src/tint/lang/wgsl/program/clone_context_fuzz.cc
+++ b/src/tint/lang/wgsl/program/clone_context_fuzz.cc
@@ -50,7 +50,7 @@
 #define ASSERT_TRUE(A)                                                                           \
     do {                                                                                         \
         decltype(A) assert_a = (A);                                                              \
-        if (!assert_a) {                                                                         \
+        if (assert_a != Success) {                                                               \
             TINT_ICE() << "ASSERT_TRUE(" #A ") failed:\n" << #A << " was: " << assert_a << "\n"; \
         }                                                                                        \
     } while (false)
diff --git a/src/tint/lang/wgsl/reader/lower/lower.cc b/src/tint/lang/wgsl/reader/lower/lower.cc
index 6b67a5e..bd218d7 100644
--- a/src/tint/lang/wgsl/reader/lower/lower.cc
+++ b/src/tint/lang/wgsl/reader/lower/lower.cc
@@ -171,7 +171,7 @@
 }  // namespace
 
 Result<SuccessType> Lower(core::ir::Module& mod) {
-    if (auto res = core::ir::ValidateAndDumpIfNeeded(mod, "lowering from WGSL"); !res) {
+    if (auto res = core::ir::ValidateAndDumpIfNeeded(mod, "lowering from WGSL"); res != Success) {
         return res.Failure();
     }
 
diff --git a/src/tint/lang/wgsl/reader/parser/lexer.cc b/src/tint/lang/wgsl/reader/parser/lexer.cc
index 102dffa..a133cb6 100644
--- a/src/tint/lang/wgsl/reader/parser/lexer.cc
+++ b/src/tint/lang/wgsl/reader/parser/lexer.cc
@@ -430,8 +430,9 @@
     }
 
     auto ret = tint::strconv::ParseDouble(std::string_view(&at(start), end - start));
-    double value = ret ? ret.Get() : 0.0;
-    bool overflow = !ret && ret.Failure() == tint::strconv::ParseNumberError::kResultOutOfRange;
+    double value = ret == Success ? ret.Get() : 0.0;
+    bool overflow =
+        ret != Success && ret.Failure() == tint::strconv::ParseNumberError::kResultOutOfRange;
 
     // If the value didn't fit in a double, check for underflow as that is 0.0 in WGSL and not an
     // error.
@@ -466,7 +467,7 @@
 
     if (has_f_suffix) {
         auto f = core::CheckedConvert<f32>(AFloat(value));
-        if (!overflow && f) {
+        if (!overflow && f == Success) {
             advance(1);
             end_source(source);
             return Token{Token::Type::kFloatLiteral_F, source, static_cast<double>(f.Get())};
@@ -476,7 +477,7 @@
 
     if (has_h_suffix) {
         auto f = core::CheckedConvert<f16>(AFloat(value));
-        if (!overflow && f) {
+        if (!overflow && f == Success) {
             advance(1);
             end_source(source);
             return Token{Token::Type::kFloatLiteral_H, source, static_cast<double>(f.Get())};
@@ -909,7 +910,7 @@
     advance(static_cast<size_t>(res.ptr - start_ptr) + prefix_count);
 
     if (matches(pos(), 'u')) {
-        if (!overflow && core::CheckedConvert<u32>(AInt(value))) {
+        if (!overflow && core::CheckedConvert<u32>(AInt(value)) == Success) {
             advance(1);
             end_source(source);
             return {Token::Type::kIntLiteral_U, source, value};
@@ -918,7 +919,7 @@
     }
 
     if (matches(pos(), 'i')) {
-        if (!overflow && core::CheckedConvert<i32>(AInt(value))) {
+        if (!overflow && core::CheckedConvert<i32>(AInt(value)) == Success) {
             advance(1);
             end_source(source);
             return {Token::Type::kIntLiteral_I, source, value};
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/accessor_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/accessor_test.cc
index 9e666f9..9e0ffa9 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/accessor_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/accessor_test.cc
@@ -51,7 +51,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -78,7 +78,7 @@
     WrapInFunction(Decl(a), expr, expr2);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -103,7 +103,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -126,7 +126,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -150,7 +150,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -178,7 +178,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(MyStruct = struct @align(4) {
@@ -215,7 +215,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(Inner = struct @align(4) {
@@ -261,7 +261,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(Inner = struct @align(16) {
@@ -296,7 +296,7 @@
     WrapInFunction(Decl(a), assign);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -319,7 +319,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -342,7 +342,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -366,7 +366,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -398,7 +398,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(MyStruct = struct @align(16) {
@@ -429,7 +429,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -452,7 +452,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -479,7 +479,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(MyStruct = struct @align(4) {
@@ -515,7 +515,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(Inner = struct @align(4) {
@@ -560,7 +560,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(Inner = struct @align(16) {
@@ -594,7 +594,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -617,7 +617,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -640,7 +640,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -671,7 +671,7 @@
     WrapInFunction(Decl(a), expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(MyStruct = struct @align(16) {
@@ -704,7 +704,7 @@
     WrapInFunction(v, i, b);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -729,7 +729,7 @@
     WrapInFunction(v, i, b);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -754,7 +754,7 @@
     WrapInFunction(v, i, b);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/binary_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/binary_test.cc
index 11df6fa..9903836 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/binary_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/binary_test.cc
@@ -44,7 +44,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -68,7 +68,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, u32, read_write> = var
@@ -91,7 +91,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, u32, read_write> = var
@@ -114,7 +114,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -138,7 +138,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, i32, read_write> = var
@@ -161,7 +161,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, u32, read_write> = var
@@ -184,7 +184,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -208,7 +208,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, u32, read_write> = var
@@ -231,7 +231,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -255,7 +255,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, u32, read_write> = var
@@ -278,7 +278,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -302,7 +302,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, u32, read_write> = var
@@ -325,7 +325,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -349,7 +349,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, bool, read_write> = var
@@ -372,7 +372,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -396,7 +396,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, bool, read_write> = var
@@ -419,7 +419,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -443,7 +443,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, u32, read_write> = var
@@ -467,7 +467,7 @@
     WrapInFunction(let, expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():bool -> %b1 {
   %b1 = block {
@@ -504,7 +504,7 @@
     WrapInFunction(let, expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():bool -> %b1 {
   %b1 = block {
@@ -540,7 +540,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -564,7 +564,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -588,7 +588,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -612,7 +612,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -636,7 +636,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -660,7 +660,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -684,7 +684,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -694,7 +694,7 @@
 %test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b2 {
   %b2 = block {
     %3:u32 = call %my_func
-    %4:u32 = shiftl %3, 4u
+    %4:u32 = shl %3, 4u
     %tint_symbol:u32 = let %4
     ret
   }
@@ -708,7 +708,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, u32, read_write> = var
@@ -717,7 +717,7 @@
 %test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b2 {
   %b2 = block {
     %3:u32 = load %v1
-    %4:u32 = shiftl %3, 1u
+    %4:u32 = shl %3, 1u
     store %v1, %4
     ret
   }
@@ -731,7 +731,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -741,7 +741,7 @@
 %test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b2 {
   %b2 = block {
     %3:u32 = call %my_func
-    %4:u32 = shiftr %3, 4u
+    %4:u32 = shr %3, 4u
     %tint_symbol:u32 = let %4
     ret
   }
@@ -755,7 +755,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, u32, read_write> = var
@@ -764,7 +764,7 @@
 %test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b2 {
   %b2 = block {
     %3:u32 = load %v1
-    %4:u32 = shiftr %3, 1u
+    %4:u32 = shr %3, 1u
     store %v1, %4
     ret
   }
@@ -780,7 +780,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():f32 -> %b1 {
   %b1 = block {
@@ -818,7 +818,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func(%p:bool):bool -> %b1 {
   %b1 = block {
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/builtin_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/builtin_test.cc
index b92e960..fecb029 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/builtin_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/builtin_test.cc
@@ -44,7 +44,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %i:ptr<private, f32, read_write> = var, 1.0f
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/call_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/call_test.cc
index 52cd00c..a024c57 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/call_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/call_test.cc
@@ -46,7 +46,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():f32 -> %b1 {
   %b1 = block {
@@ -72,7 +72,7 @@
          });
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%test_function = @fragment func():void -> %b1 {
   %b1 = block {
@@ -89,7 +89,7 @@
     auto* stmt = CallStmt(Call("my_func", Mul(2_a, 3_a)));
     WrapInFunction(stmt);
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func(%p:f32):void -> %b1 {
   %b1 = block {
@@ -111,7 +111,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %i:ptr<private, i32, read_write> = var, 1i
@@ -133,7 +133,7 @@
     GlobalVar("i", core::AddressSpace::kPrivate, expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %i:ptr<private, vec3<f32>, read_write> = var, vec3<f32>(0.0f)
@@ -148,7 +148,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %i:ptr<private, f32, read_write> = var, 1.0f
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/function_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/function_test.cc
index 4bb5446..da4dd6b 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/function_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/function_test.cc
@@ -45,7 +45,7 @@
          Vector{Builtin(core::BuiltinValue::kPosition)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%test = @vertex func():vec4<f32> [@position] -> %b1 {
   %b1 = block {
@@ -60,7 +60,7 @@
          Vector{Stage(ast::PipelineStage::kFragment)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%test = @fragment func():void -> %b1 {
   %b1 = block {
@@ -75,7 +75,7 @@
          Vector{Stage(ast::PipelineStage::kCompute), WorkgroupSize(8_i, 4_i, 2_i)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test = @compute @workgroup_size(8, 4, 2) func():void -> %b1 {
@@ -91,7 +91,7 @@
          tint::Empty);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%test = func():vec3<f32> -> %b1 {
   %b1 = block {
@@ -106,7 +106,7 @@
          Vector{If(true, Block(Return(0_f)), Else(Block(Return(1_f))))}, tint::Empty);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%test = func():f32 -> %b1 {
   %b1 = block {
@@ -130,7 +130,7 @@
          Vector{Builtin(core::BuiltinValue::kPosition)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%test = @vertex func():vec4<f32> [@position] -> %b1 {
   %b1 = block {
@@ -146,7 +146,7 @@
          Vector{Builtin(core::BuiltinValue::kPosition), Invariant()});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test = @vertex func():vec4<f32> [@invariant, @position] -> %b1 {
@@ -162,7 +162,7 @@
          Vector{Stage(ast::PipelineStage::kFragment)}, Vector{Location(1_i)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test = @fragment func():vec4<f32> [@location(1)] -> %b1 {
@@ -180,7 +180,7 @@
                                            core::InterpolationSampling::kCentroid)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(
         Disassemble(m.Get()),
@@ -198,7 +198,7 @@
          Vector{Builtin(core::BuiltinValue::kFragDepth)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%test = @fragment func():f32 [@frag_depth] -> %b1 {
   %b1 = block {
@@ -214,7 +214,7 @@
          Vector{Builtin(core::BuiltinValue::kSampleMask)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%test = @fragment func():u32 [@sample_mask] -> %b1 {
   %b1 = block {
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/let_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/let_test.cc
index 9d4df69..bfce845 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/let_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/let_test.cc
@@ -42,7 +42,7 @@
     WrapInFunction(Let("a", Expr(42_i)));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -58,7 +58,7 @@
     WrapInFunction(Let("a", Add(1_i, 2_i)));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -76,7 +76,7 @@
                    Let("c", Expr("b")));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/literal_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/literal_test.cc
index 1e05175..e56a0c6 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/literal_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/literal_test.cc
@@ -64,7 +64,7 @@
     GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate, expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     auto* init = GlobalVarInitializer(m.Get());
     ASSERT_TRUE(Is<core::ir::Constant>(init));
@@ -78,7 +78,7 @@
     GlobalVar("a", ty.bool_(), core::AddressSpace::kPrivate, expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     auto* init = GlobalVarInitializer(m.Get());
     ASSERT_TRUE(Is<core::ir::Constant>(init));
@@ -94,7 +94,7 @@
     GlobalVar("d", ty.bool_(), core::AddressSpace::kPrivate, Expr(false));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     auto itr = m.Get().root_block->begin();
     auto* var_a = (*itr)->As<core::ir::Var>();
@@ -122,7 +122,7 @@
     GlobalVar("a", ty.f32(), core::AddressSpace::kPrivate, expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     auto* init = GlobalVarInitializer(m.Get());
     ASSERT_TRUE(Is<core::ir::Constant>(init));
@@ -137,7 +137,7 @@
     GlobalVar("c", ty.f32(), core::AddressSpace::kPrivate, Expr(1.2_f));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     auto itr = m.Get().root_block->begin();
     auto* var_a = (*itr)->As<core::ir::Var>();
@@ -161,7 +161,7 @@
     GlobalVar("a", ty.f16(), core::AddressSpace::kPrivate, expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     auto* init = GlobalVarInitializer(m.Get());
     ASSERT_TRUE(Is<core::ir::Constant>(init));
@@ -177,7 +177,7 @@
     GlobalVar("c", ty.f16(), core::AddressSpace::kPrivate, Expr(1.2_h));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     auto itr = m.Get().root_block->begin();
     auto* var_a = (*itr)->As<core::ir::Var>();
@@ -200,7 +200,7 @@
     GlobalVar("a", ty.i32(), core::AddressSpace::kPrivate, expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     auto* init = GlobalVarInitializer(m.Get());
     ASSERT_TRUE(Is<core::ir::Constant>(init));
@@ -215,7 +215,7 @@
     GlobalVar("c", ty.i32(), core::AddressSpace::kPrivate, Expr(-2_i));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     auto itr = m.Get().root_block->begin();
     auto* var_a = (*itr)->As<core::ir::Var>();
@@ -238,7 +238,7 @@
     GlobalVar("a", ty.u32(), core::AddressSpace::kPrivate, expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     auto* init = GlobalVarInitializer(m.Get());
     ASSERT_TRUE(Is<core::ir::Constant>(init));
@@ -253,7 +253,7 @@
     GlobalVar("c", ty.u32(), core::AddressSpace::kPrivate, Expr(2_u));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     auto itr = m.Get().root_block->begin();
     auto* var_a = (*itr)->As<core::ir::Var>();
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/materialize_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/materialize_test.cc
index e5db933..6891335 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/materialize_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/materialize_test.cc
@@ -44,7 +44,7 @@
     Func("test_function", {}, ty.f32(), expr, tint::Empty);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%test_function = func():f32 -> %b1 {
   %b1 = block {
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
index 4644964..0ae2eea 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.cc
@@ -340,24 +340,7 @@
                                 program_.Sem()
                                     .Get(b)
                                     ->As<sem::BuiltinEnumExpression<core::BuiltinValue>>()) {
-                            switch (ident_sem->Value()) {
-                                case core::BuiltinValue::kPosition:
-                                    ir_func->SetReturnBuiltin(
-                                        core::ir::Function::ReturnBuiltin::kPosition);
-                                    break;
-                                case core::BuiltinValue::kFragDepth:
-                                    ir_func->SetReturnBuiltin(
-                                        core::ir::Function::ReturnBuiltin::kFragDepth);
-                                    break;
-                                case core::BuiltinValue::kSampleMask:
-                                    ir_func->SetReturnBuiltin(
-                                        core::ir::Function::ReturnBuiltin::kSampleMask);
-                                    break;
-                                default:
-                                    TINT_ICE() << "Unknown builtin value in return attributes "
-                                               << ident_sem->Value();
-                                    return;
-                            }
+                            ir_func->SetReturnBuiltin(ident_sem->Value());
                         } else {
                             TINT_ICE() << "Builtin attribute sem invalid";
                             return;
@@ -393,63 +376,7 @@
                                 program_.Sem()
                                     .Get(b)
                                     ->As<sem::BuiltinEnumExpression<core::BuiltinValue>>()) {
-                            switch (ident_sem->Value()) {
-                                case core::BuiltinValue::kVertexIndex:
-                                    param->SetBuiltin(
-                                        core::ir::FunctionParam::Builtin::kVertexIndex);
-                                    break;
-                                case core::BuiltinValue::kInstanceIndex:
-                                    param->SetBuiltin(
-                                        core::ir::FunctionParam::Builtin::kInstanceIndex);
-                                    break;
-                                case core::BuiltinValue::kPosition:
-                                    param->SetBuiltin(core::ir::FunctionParam::Builtin::kPosition);
-                                    break;
-                                case core::BuiltinValue::kFrontFacing:
-                                    param->SetBuiltin(
-                                        core::ir::FunctionParam::Builtin::kFrontFacing);
-                                    break;
-                                case core::BuiltinValue::kLocalInvocationId:
-                                    param->SetBuiltin(
-                                        core::ir::FunctionParam::Builtin::kLocalInvocationId);
-                                    break;
-                                case core::BuiltinValue::kLocalInvocationIndex:
-                                    param->SetBuiltin(
-                                        core::ir::FunctionParam::Builtin::kLocalInvocationIndex);
-                                    break;
-                                case core::BuiltinValue::kGlobalInvocationId:
-                                    param->SetBuiltin(
-                                        core::ir::FunctionParam::Builtin::kGlobalInvocationId);
-                                    break;
-                                case core::BuiltinValue::kWorkgroupId:
-                                    param->SetBuiltin(
-                                        core::ir::FunctionParam::Builtin::kWorkgroupId);
-                                    break;
-                                case core::BuiltinValue::kNumWorkgroups:
-                                    param->SetBuiltin(
-                                        core::ir::FunctionParam::Builtin::kNumWorkgroups);
-                                    break;
-                                case core::BuiltinValue::kSampleIndex:
-                                    param->SetBuiltin(
-                                        core::ir::FunctionParam::Builtin::kSampleIndex);
-                                    break;
-                                case core::BuiltinValue::kSampleMask:
-                                    param->SetBuiltin(
-                                        core::ir::FunctionParam::Builtin::kSampleMask);
-                                    break;
-                                case core::BuiltinValue::kSubgroupInvocationId:
-                                    param->SetBuiltin(
-                                        core::ir::FunctionParam::Builtin::kSubgroupInvocationId);
-                                    break;
-                                case core::BuiltinValue::kSubgroupSize:
-                                    param->SetBuiltin(
-                                        core::ir::FunctionParam::Builtin::kSubgroupSize);
-                                    break;
-                                default:
-                                    TINT_ICE() << "Unknown builtin value in parameter attributes "
-                                               << ident_sem->Value();
-                                    return;
-                            }
+                            param->SetBuiltin(ident_sem->Value());
                         } else {
                             TINT_ICE() << "Builtin attribute sem invalid";
                             return;
@@ -1417,7 +1344,7 @@
 
     Impl b(program);
     auto r = b.Build();
-    if (!r) {
+    if (r != Success) {
         diag::List err = std::move(r.Failure().reason);
         err.add_note(diag::System::IR, "AST:\n" + Program::printer(program), Source{});
         return Failure{err};
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir_test.cc
index bd204c0..b47ee3a 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/program_to_ir_test.cc
@@ -73,7 +73,7 @@
     Func("f", tint::Empty, ty.void_(), tint::Empty);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     ASSERT_EQ(1u, m->functions.Length());
 
@@ -94,7 +94,7 @@
     Func("f", Vector{Param("a", ty.u32())}, ty.u32(), Vector{Return("a")});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     ASSERT_EQ(1u, m->functions.Length());
 
@@ -116,7 +116,7 @@
          ty.void_(), tint::Empty);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     ASSERT_EQ(1u, m->functions.Length());
 
@@ -137,7 +137,7 @@
     Func("f", tint::Empty, ty.void_(), tint::Empty, Vector{Stage(ast::PipelineStage::kFragment)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(m->functions[0]->Stage(), core::ir::Function::PipelineStage::kFragment);
 }
@@ -147,7 +147,7 @@
     WrapInFunction(ast_if);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
 
@@ -175,7 +175,7 @@
     WrapInFunction(ast_if);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
 
@@ -200,7 +200,7 @@
     WrapInFunction(ast_if);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
 
@@ -228,7 +228,7 @@
     WrapInFunction(ast_if);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
 
@@ -257,7 +257,7 @@
     WrapInFunction(ast_if);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
 
@@ -288,7 +288,7 @@
     WrapInFunction(ast_loop);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* loop = FindSingleInstruction<core::ir::Loop>(m);
@@ -321,7 +321,7 @@
     WrapInFunction(ast_loop);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* loop = FindSingleInstruction<core::ir::Loop>(m);
@@ -359,7 +359,7 @@
     WrapInFunction(ast_loop);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* loop = FindSingleInstruction<core::ir::Loop>(m);
@@ -393,7 +393,7 @@
     WrapInFunction(ast_loop);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     EXPECT_EQ(Disassemble(m),
@@ -420,7 +420,7 @@
     WrapInFunction(ast_loop);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* loop = FindSingleInstruction<core::ir::Loop>(m);
@@ -457,7 +457,7 @@
     WrapInFunction(ast_loop, If(true, Block(Return())));
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* loop = FindSingleInstruction<core::ir::Loop>(m);
@@ -498,7 +498,7 @@
     WrapInFunction(Block(ast_loop, ast_if));
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* loop = FindSingleInstruction<core::ir::Loop>(m);
@@ -536,7 +536,7 @@
     WrapInFunction(ast_loop);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* loop = FindSingleInstruction<core::ir::Loop>(m);
@@ -586,7 +586,7 @@
     WrapInFunction(ast_loop_a);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -649,7 +649,7 @@
     WrapInFunction(ast_while);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* loop = FindSingleInstruction<core::ir::Loop>(m);
@@ -689,7 +689,7 @@
     WrapInFunction(ast_while);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* loop = FindSingleInstruction<core::ir::Loop>(m);
@@ -729,7 +729,7 @@
     WrapInFunction(ast_for);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* loop = FindSingleInstruction<core::ir::Loop>(m);
@@ -778,7 +778,7 @@
     WrapInFunction(ast_for);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* loop = FindSingleInstruction<core::ir::Loop>(m);
@@ -811,7 +811,7 @@
     WrapInFunction(ast_for);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* loop = FindSingleInstruction<core::ir::Loop>(m);
@@ -843,7 +843,7 @@
     WrapInFunction(ast_switch);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* swtch = FindSingleInstruction<core::ir::Switch>(m);
@@ -894,7 +894,7 @@
     WrapInFunction(ast_switch);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* swtch = FindSingleInstruction<core::ir::Switch>(m);
@@ -933,7 +933,7 @@
     WrapInFunction(ast_switch);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* swtch = FindSingleInstruction<core::ir::Switch>(m);
@@ -966,7 +966,7 @@
     WrapInFunction(ast_switch);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
     auto* swtch = FindSingleInstruction<core::ir::Switch>(m);
@@ -1009,7 +1009,7 @@
     WrapInFunction(ast_switch, ast_if);
 
     auto res = Build();
-    ASSERT_TRUE(res) << (!res ? res.Failure() : Failure{});
+    ASSERT_EQ(res, Success);
 
     auto m = res.Move();
 
@@ -1049,7 +1049,7 @@
     WrapInFunction(Ignore(Call("b")));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%b = func():i32 -> %b1 {
@@ -1073,7 +1073,7 @@
          ty.vec4<f32>(), Vector{Return("a")}, Vector{Stage(ast::PipelineStage::kFragment)},
          Vector{Location(1_i)});
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(
         Disassemble(m.Get()),
@@ -1090,7 +1090,7 @@
          Vector{Stage(ast::PipelineStage::kFragment)}, Vector{Location(1_i)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%f = @fragment func(%a:f32 [@location(2)]):f32 [@location(1)] -> %b1 {
@@ -1110,7 +1110,7 @@
          Vector{Location(1_i)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(
         Disassemble(m.Get()),
@@ -1130,7 +1130,7 @@
          Vector{Location(1_i)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(
         Disassemble(m.Get()),
@@ -1147,7 +1147,7 @@
     Func("f", tint::Empty, ty.void_(), tint::Empty);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     ASSERT_EQ(1u, m->functions.Length());
 
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/shadowing_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/shadowing_test.cc
index 09af944..f01b4da 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/shadowing_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/shadowing_test.cc
@@ -57,7 +57,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(S = struct @align(4) {
   i:i32 @offset(0)
@@ -85,7 +85,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(S = struct @align(4) {
   i:i32 @offset(0)
@@ -111,7 +111,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %i:ptr<private, i32, read_write> = var, 1i
@@ -143,7 +143,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %i:ptr<private, i32, read_write> = var, 1i
@@ -176,7 +176,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -215,7 +215,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -250,7 +250,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -296,7 +296,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -340,7 +340,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -384,7 +384,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -427,7 +427,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -474,7 +474,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -525,7 +525,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -579,7 +579,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -633,7 +633,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -683,7 +683,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -733,7 +733,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
@@ -781,7 +781,7 @@
 }
 )");
 
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%f = func():i32 -> %b1 {
   %b1 = block {
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/store_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/store_test.cc
index c7478b9..c2f9ad1 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/store_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/store_test.cc
@@ -45,7 +45,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %a:ptr<private, u32, read_write> = var
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/unary_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/unary_test.cc
index 98ff492..62c7e6f 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/unary_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/unary_test.cc
@@ -44,7 +44,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():bool -> %b1 {
   %b1 = block {
@@ -68,7 +68,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():vec4<bool> -> %b1 {
   %b1 = block {
@@ -92,7 +92,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():u32 -> %b1 {
   %b1 = block {
@@ -116,7 +116,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%my_func = func():i32 -> %b1 {
   %b1 = block {
@@ -141,7 +141,7 @@
     WrapInFunction(expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, i32, read_write> = var
@@ -165,7 +165,7 @@
     WrapInFunction(stmts);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %v1:ptr<private, i32, read_write> = var
diff --git a/src/tint/lang/wgsl/reader/program_to_ir/var_test.cc b/src/tint/lang/wgsl/reader/program_to_ir/var_test.cc
index 57556e6..b846045 100644
--- a/src/tint/lang/wgsl/reader/program_to_ir/var_test.cc
+++ b/src/tint/lang/wgsl/reader/program_to_ir/var_test.cc
@@ -43,7 +43,7 @@
     GlobalVar("a", ty.u32(), core::AddressSpace::kPrivate);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %a:ptr<private, u32, read_write> = var
@@ -57,7 +57,7 @@
     GlobalVar("a", ty.u32(), core::AddressSpace::kPrivate, expr);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %a:ptr<private, u32, read_write> = var, 2u
@@ -70,7 +70,7 @@
     GlobalVar("a", ty.u32(), core::AddressSpace::kStorage, Vector{Group(2_u), Binding(3_u)});
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()), R"(%b1 = block {  # root
   %a:ptr<storage, u32, read> = var @binding_point(2, 3)
@@ -84,7 +84,7 @@
     WrapInFunction(a);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -102,7 +102,7 @@
     WrapInFunction(a);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -120,7 +120,7 @@
     WrapInFunction(a, b);
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -140,7 +140,7 @@
                    Assign("a", 42_i));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -177,7 +177,7 @@
         Assign(lhs, rhs));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%f = func(%p:i32):i32 -> %b1 {
@@ -223,7 +223,7 @@
                    Assign(lhs, rhs));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%f = func(%p:i32):i32 -> %b1 {
@@ -271,7 +271,7 @@
                    Assign(lhs, rhs));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%f = func(%p:i32):i32 -> %b1 {
@@ -303,7 +303,7 @@
                    CompoundAssign("a", 42_i, core::BinaryOp::kAdd));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%test_function = @compute @workgroup_size(1, 1, 1) func():void -> %b1 {
@@ -342,7 +342,7 @@
         CompoundAssign(lhs, rhs, core::BinaryOp::kAdd));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%f = func(%p:i32):i32 -> %b1 {
@@ -394,7 +394,7 @@
                    CompoundAssign(lhs, rhs, core::BinaryOp::kAdd));
 
     auto m = Build();
-    ASSERT_TRUE(m) << m;
+    ASSERT_EQ(m, Success);
 
     EXPECT_EQ(Disassemble(m.Get()),
               R"(%f = func(%p:i32):i32 -> %b1 {
diff --git a/src/tint/lang/wgsl/reader/reader.cc b/src/tint/lang/wgsl/reader/reader.cc
index f74d837..3ddc2ae 100644
--- a/src/tint/lang/wgsl/reader/reader.cc
+++ b/src/tint/lang/wgsl/reader/reader.cc
@@ -45,11 +45,11 @@
 Result<core::ir::Module> WgslToIR(const Source::File* file, const Options& options) {
     Program program = Parse(file, options);
     auto module = ProgramToIR(program);
-    if (!module) {
+    if (module != Success) {
         return module.Failure();
     }
     // WGSL-dialect -> core-dialect
-    if (auto res = Lower(module.Get()); !res) {
+    if (auto res = Lower(module.Get()); res != Success) {
         return res.Failure();
     }
     return module;
@@ -57,13 +57,13 @@
 
 tint::Result<core::ir::Module> ProgramToLoweredIR(const Program& program) {
     auto ir = tint::wgsl::reader::ProgramToIR(program);
-    if (!ir) {
+    if (ir != Success) {
         return ir.Failure();
     }
 
     // Lower from WGSL-dialect to core-dialect
     auto res = tint::wgsl::reader::Lower(ir.Get());
-    if (!res) {
+    if (res != Success) {
         return res.Failure();
     }
 
diff --git a/src/tint/lang/wgsl/reader/reader_bench.cc b/src/tint/lang/wgsl/reader/reader_bench.cc
index 4fc193e..bc24e0d 100644
--- a/src/tint/lang/wgsl/reader/reader_bench.cc
+++ b/src/tint/lang/wgsl/reader/reader_bench.cc
@@ -35,7 +35,7 @@
 
 void ParseWGSL(benchmark::State& state, std::string input_name) {
     auto res = bench::LoadInputFile(input_name);
-    if (!res) {
+    if (res != Success) {
         state.SkipWithError(res.Failure().reason.str());
         return;
     }
diff --git a/src/tint/lang/wgsl/resolver/resolver.cc b/src/tint/lang/wgsl/resolver/resolver.cc
index a155f8c..9bc73d6 100644
--- a/src/tint/lang/wgsl/resolver/resolver.cc
+++ b/src/tint/lang/wgsl/resolver/resolver.cc
@@ -608,7 +608,7 @@
                 attribute,  //
                 [&](const ast::BindingAttribute* attr) {
                     auto value = BindingAttribute(attr);
-                    if (!value) {
+                    if (value != Success) {
                         return kErrored;
                     }
                     binding = value.Get();
@@ -616,7 +616,7 @@
                 },
                 [&](const ast::GroupAttribute* attr) {
                     auto value = GroupAttribute(attr);
-                    if (!value) {
+                    if (value != Success) {
                         return kErrored;
                     }
                     group = value.Get();
@@ -627,7 +627,7 @@
                         return kInvalid;
                     }
                     auto value = LocationAttribute(attr);
-                    if (!value) {
+                    if (value != Success) {
                         return kErrored;
                     }
                     global->Attributes().location = value.Get();
@@ -638,7 +638,7 @@
                         return kInvalid;
                     }
                     auto value = IndexAttribute(attr);
-                    if (!value) {
+                    if (value != Success) {
                         return kErrored;
                     }
                     global->Attributes().index = value.Get();
@@ -649,7 +649,7 @@
                         return kInvalid;
                     }
                     auto value = ColorAttribute(attr);
-                    if (!value) {
+                    if (value != Success) {
                         return kErrored;
                     }
                     global->Attributes().color = value.Get();
@@ -659,13 +659,13 @@
                     if (!has_io_address_space) {
                         return kInvalid;
                     }
-                    return BuiltinAttribute(attr) ? kSuccess : kErrored;
+                    return BuiltinAttribute(attr) == Success ? kSuccess : kErrored;
                 },
                 [&](const ast::InterpolateAttribute* attr) {
                     if (!has_io_address_space) {
                         return kInvalid;
                     }
-                    return InterpolateAttribute(attr) ? kSuccess : kErrored;
+                    return InterpolateAttribute(attr) == Success ? kSuccess : kErrored;
                 },
                 [&](const ast::InvariantAttribute* attr) {
                     if (!has_io_address_space) {
@@ -732,7 +732,7 @@
                 attribute,  //
                 [&](const ast::LocationAttribute* attr) {
                     auto value = LocationAttribute(attr);
-                    if (TINT_UNLIKELY(!value)) {
+                    if (TINT_UNLIKELY(value != Success)) {
                         return false;
                     }
                     sem->Attributes().location = value.Get();
@@ -740,28 +740,30 @@
                 },
                 [&](const ast::ColorAttribute* attr) {
                     auto value = ColorAttribute(attr);
-                    if (TINT_UNLIKELY(!value)) {
+                    if (TINT_UNLIKELY(value != Success)) {
                         return false;
                     }
                     sem->Attributes().color = value.Get();
                     return true;
                 },
-                [&](const ast::BuiltinAttribute* attr) -> bool { return BuiltinAttribute(attr); },
+                [&](const ast::BuiltinAttribute* attr) {
+                    return BuiltinAttribute(attr) == Success;
+                },
                 [&](const ast::InvariantAttribute* attr) -> bool {
                     return InvariantAttribute(attr);
                 },
-                [&](const ast::InterpolateAttribute* attr) -> bool {
-                    return InterpolateAttribute(attr);
+                [&](const ast::InterpolateAttribute* attr) {
+                    return InterpolateAttribute(attr) == Success;
                 },
                 [&](const ast::InternalAttribute* attr) -> bool { return InternalAttribute(attr); },
-                [&](const ast::GroupAttribute* attr) -> bool {
+                [&](const ast::GroupAttribute* attr) {
                     if (validator_.IsValidationEnabled(
                             param->attributes, ast::DisabledValidation::kEntryPointParameter)) {
                         ErrorInvalidAttribute(attribute, "function parameters");
                         return false;
                     }
                     auto value = GroupAttribute(attr);
-                    if (TINT_UNLIKELY(!value)) {
+                    if (TINT_UNLIKELY(value != Success)) {
                         return false;
                     }
                     group = value.Get();
@@ -774,7 +776,7 @@
                         return false;
                     }
                     auto value = BindingAttribute(attr);
-                    if (TINT_UNLIKELY(!value)) {
+                    if (TINT_UNLIKELY(value != Success)) {
                         return false;
                     }
                     binding = value.Get();
@@ -987,7 +989,7 @@
             [&](const ast::MustUseAttribute* attr) { return MustUseAttribute(attr); },
             [&](const ast::WorkgroupAttribute* attr) {
                 auto value = WorkgroupAttribute(attr);
-                if (!value) {
+                if (value != Success) {
                     return false;
                 }
                 func->SetWorkgroupSize(value.Get());
@@ -1071,7 +1073,7 @@
                 attribute,  //
                 [&](const ast::LocationAttribute* attr) {
                     auto value = LocationAttribute(attr);
-                    if (!value) {
+                    if (value != Success) {
                         return kErrored;
                     }
                     func->SetReturnLocation(value.Get());
@@ -1079,20 +1081,20 @@
                 },
                 [&](const ast::IndexAttribute* attr) {
                     auto value = IndexAttribute(attr);
-                    if (!value) {
+                    if (value != Success) {
                         return kErrored;
                     }
                     func->SetReturnIndex(value.Get());
                     return kSuccess;
                 },
                 [&](const ast::BuiltinAttribute* attr) {
-                    return BuiltinAttribute(attr) ? kSuccess : kErrored;
+                    return BuiltinAttribute(attr) == Success ? kSuccess : kErrored;
                 },
                 [&](const ast::InternalAttribute* attr) {
                     return InternalAttribute(attr) ? kSuccess : kErrored;
                 },
                 [&](const ast::InterpolateAttribute* attr) {
-                    return InterpolateAttribute(attr) ? kSuccess : kErrored;
+                    return InterpolateAttribute(attr) == Success ? kSuccess : kErrored;
                 },
                 [&](const ast::InvariantAttribute* attr) {
                     return InvariantAttribute(attr) ? kSuccess : kErrored;
@@ -1101,13 +1103,13 @@
                     if (!permissive) {
                         return kInvalid;
                     }
-                    return BindingAttribute(attr) ? kSuccess : kErrored;
+                    return BindingAttribute(attr) == Success ? kSuccess : kErrored;
                 },
                 [&](const ast::GroupAttribute* attr) {
                     if (!permissive) {
                         return kInvalid;
                     }
-                    return GroupAttribute(attr) ? kSuccess : kErrored;
+                    return GroupAttribute(attr) == Success ? kSuccess : kErrored;
                 },
                 [&](Default) { return kInvalid; });
 
@@ -1889,7 +1891,7 @@
         }
 
         auto val = const_eval_.Convert(concrete_ty, expr_val, decl->source);
-        if (!val) {
+        if (val != Success) {
             // Convert() has already failed and raised an diagnostic error.
             return nullptr;
         }
@@ -1941,7 +1943,7 @@
                        const core::type::Type* target_ty,
                        const Source& source) {
     auto r = const_eval_.Convert(target_ty, c, source);
-    if (!r) {
+    if (r != Success) {
         return false;
     }
     c = r.Get();
@@ -2014,7 +2016,7 @@
         if (auto* idx_val = idx->ConstantValue()) {
             auto res = const_eval_.Index(obj->ConstantValue(), obj->Type(), idx_val,
                                          idx->Declaration()->source);
-            if (!res) {
+            if (res != Success) {
                 return nullptr;
             }
             val = res.Get();
@@ -2048,11 +2050,11 @@
 
     const core::constant::Value* value = nullptr;
     if (stage == core::EvaluationStage::kConstant) {
-        if (auto r = const_eval_.Bitcast(ty, inner->ConstantValue(), expr->source)) {
-            value = r.Get();
-        } else {
+        auto r = const_eval_.Bitcast(ty, inner->ConstantValue(), expr->source);
+        if (r != Success) {
             return nullptr;
         }
+        value = r.Get();
     }
 
     auto* sem = b.create<sem::ValueExpression>(expr, ty, stage, current_statement_,
@@ -2096,9 +2098,9 @@
     // sem::ValueConversion call for a CtorConvIntrinsic with an optional template argument type.
     auto ctor_or_conv = [&](CtorConvIntrinsic ty,
                             const core::type::Type* template_arg) -> sem::Call* {
-        auto arg_tys = tint::Transform(args, [](auto* arg) { return arg->Type(); });
+        auto arg_tys = tint::Transform(args, [](auto* arg) { return arg->Type()->UnwrapRef(); });
         auto match = intrinsic_table_.Lookup(ty, template_arg, arg_tys, args_stage, expr->source);
-        if (!match) {
+        if (match != Success) {
             return nullptr;
         }
 
@@ -2137,16 +2139,16 @@
         }
         if (stage == core::EvaluationStage::kConstant) {
             auto const_args = ConvertArguments(args, target_sem);
-            if (!const_args) {
+            if (const_args != Success) {
                 return nullptr;
             }
             auto const_eval_fn = match->const_eval_fn;
-            if (auto r = (const_eval_.*const_eval_fn)(target_sem->ReturnType(), const_args.Get(),
-                                                      expr->source)) {
-                value = r.Get();
-            } else {
+            auto r = (const_eval_.*const_eval_fn)(target_sem->ReturnType(), const_args.Get(),
+                                                  expr->source);
+            if (r != Success) {
                 return nullptr;
             }
+            value = r.Get();
         }
         return b.create<sem::Call>(expr, target_sem, stage, std::move(args), current_statement_,
                                    value, has_side_effects);
@@ -2163,14 +2165,14 @@
         }
         if (stage == core::EvaluationStage::kConstant) {
             auto const_args = ConvertArguments(args, call_target);
-            if (!const_args) {
+            if (const_args != Success) {
                 return nullptr;
             }
-            if (auto r = const_eval_.ArrayOrStructCtor(ty, std::move(const_args.Get()))) {
-                value = r.Get();
-            } else {
+            auto r = const_eval_.ArrayOrStructCtor(ty, std::move(const_args.Get()));
+            if (r != Success) {
                 return nullptr;
             }
+            value = r.Get();
             if (!value) {
                 // Constant evaluation failed.
                 // Can happen for expressions that will fail validation (later).
@@ -2368,9 +2370,9 @@
         arg_stage = core::EarliestStage(arg_stage, arg->Stage());
     }
 
-    auto arg_tys = tint::Transform(args, [](auto* arg) { return arg->Type(); });
+    auto arg_tys = tint::Transform(args, [](auto* arg) { return arg->Type()->UnwrapRef(); });
     auto overload = intrinsic_table_.Lookup(fn, arg_tys, arg_stage, expr->source);
-    if (!overload) {
+    if (overload != Success) {
         return nullptr;
     }
 
@@ -2422,16 +2424,15 @@
     }
     if (stage == core::EvaluationStage::kConstant) {
         auto const_args = ConvertArguments(args, target);
-        if (!const_args) {
+        if (const_args != Success) {
             return nullptr;
         }
         auto const_eval_fn = overload->const_eval_fn;
-        if (auto r = (const_eval_.*const_eval_fn)(target->ReturnType(), const_args.Get(),
-                                                  expr->source)) {
-            value = r.Get();
-        } else {
+        auto r = (const_eval_.*const_eval_fn)(target->ReturnType(), const_args.Get(), expr->source);
+        if (r != Success) {
             return nullptr;
         }
+        value = r.Get();
     }
 
     bool has_side_effects =
@@ -3509,7 +3510,7 @@
             const core::constant::Value* val = nullptr;
             if (auto* obj_val = object->ConstantValue()) {
                 auto res = const_eval_.Swizzle(ty, obj_val, swizzle);
-                if (!res) {
+                if (res != Success) {
                     return nullptr;
                 }
                 val = res.Get();
@@ -3544,9 +3545,9 @@
     }
 
     auto stage = core::EarliestStage(lhs->Stage(), rhs->Stage());
-    auto overload =
-        intrinsic_table_.Lookup(expr->op, lhs->Type(), rhs->Type(), stage, expr->source, false);
-    if (!overload) {
+    auto overload = intrinsic_table_.Lookup(expr->op, lhs->Type()->UnwrapRef(),
+                                            rhs->Type()->UnwrapRef(), stage, expr->source, false);
+    if (overload != Success) {
         return nullptr;
     }
 
@@ -3592,11 +3593,11 @@
             if (!Convert(const_args[1], rhs_ty, rhs->Declaration()->source)) {
                 return nullptr;
             }
-            if (auto r = (const_eval_.*const_eval_fn)(res_ty, const_args, expr->source)) {
-                value = r.Get();
-            } else {
+            auto r = (const_eval_.*const_eval_fn)(res_ty, const_args, expr->source);
+            if (r != Success) {
                 return nullptr;
             }
+            value = r.Get();
         } else {
             // The arguments have constant values, but the operator cannot be const-evaluated.
             // This can only be evaluated at runtime.
@@ -3666,8 +3667,9 @@
 
         default: {
             stage = expr->Stage();
-            auto overload = intrinsic_table_.Lookup(unary->op, expr_ty, stage, unary->source);
-            if (!overload) {
+            auto overload =
+                intrinsic_table_.Lookup(unary->op, expr_ty->UnwrapRef(), stage, unary->source);
+            if (overload != Success) {
                 return nullptr;
             }
             ty = overload->return_type;
@@ -3688,12 +3690,12 @@
             stage = expr->Stage();
             if (stage == core::EvaluationStage::kConstant) {
                 if (auto const_eval_fn = overload->const_eval_fn) {
-                    if (auto r = (const_eval_.*const_eval_fn)(ty, Vector{expr->ConstantValue()},
-                                                              expr->Declaration()->source)) {
-                        value = r.Get();
-                    } else {
+                    auto r = (const_eval_.*const_eval_fn)(ty, Vector{expr->ConstantValue()},
+                                                          expr->Declaration()->source);
+                    if (r != Success) {
                         return nullptr;
                     }
+                    value = r.Get();
                 } else {
                     stage = core::EvaluationStage::kRuntime;
                 }
@@ -4375,7 +4377,7 @@
                 },
                 [&](const ast::LocationAttribute* attr) {
                     auto value = LocationAttribute(attr);
-                    if (!value) {
+                    if (value != Success) {
                         return false;
                     }
                     attributes.location = value.Get();
@@ -4383,7 +4385,7 @@
                 },
                 [&](const ast::IndexAttribute* attr) {
                     auto value = IndexAttribute(attr);
-                    if (!value) {
+                    if (value != Success) {
                         return false;
                     }
                     attributes.index = value.Get();
@@ -4391,7 +4393,7 @@
                 },
                 [&](const ast::ColorAttribute* attr) {
                     auto value = ColorAttribute(attr);
-                    if (!value) {
+                    if (value != Success) {
                         return false;
                     }
                     attributes.color = value.Get();
@@ -4399,7 +4401,7 @@
                 },
                 [&](const ast::BuiltinAttribute* attr) {
                     auto value = BuiltinAttribute(attr);
-                    if (!value) {
+                    if (value != Success) {
                         return false;
                     }
                     attributes.builtin = value.Get();
@@ -4407,7 +4409,7 @@
                 },
                 [&](const ast::InterpolateAttribute* attr) {
                     auto value = InterpolateAttribute(attr);
-                    if (!value) {
+                    if (value != Success) {
                         return false;
                     }
                     attributes.interpolation = value.Get();
@@ -4752,7 +4754,7 @@
         auto overload =
             intrinsic_table_.Lookup(stmt->op, lhs->Type()->UnwrapRef(), rhs->Type()->UnwrapRef(),
                                     stage, stmt->source, true);
-        if (!overload) {
+        if (overload != Success) {
             return false;
         }
 
diff --git a/src/tint/lang/wgsl/writer/BUILD.bazel b/src/tint/lang/wgsl/writer/BUILD.bazel
index ff06810..44c5f29 100644
--- a/src/tint/lang/wgsl/writer/BUILD.bazel
+++ b/src/tint/lang/wgsl/writer/BUILD.bazel
@@ -129,6 +129,11 @@
 )
 
 alias(
+  name = "tint_build_wgsl_reader",
+  actual = "//src/tint:tint_build_wgsl_reader_true",
+)
+
+alias(
   name = "tint_build_wgsl_writer",
   actual = "//src/tint:tint_build_wgsl_writer_true",
 )
diff --git a/src/tint/lang/wgsl/writer/BUILD.cmake b/src/tint/lang/wgsl/writer/BUILD.cmake
index 4e172c8..5305a89 100644
--- a/src/tint/lang/wgsl/writer/BUILD.cmake
+++ b/src/tint/lang/wgsl/writer/BUILD.cmake
@@ -139,4 +139,56 @@
   )
 endif(TINT_BUILD_WGSL_WRITER)
 
+endif(TINT_BUILD_WGSL_WRITER)
+if(TINT_BUILD_WGSL_WRITER)
+################################################################################
+# Target:    tint_lang_wgsl_writer_fuzz
+# Kind:      fuzz
+# Condition: TINT_BUILD_WGSL_WRITER
+################################################################################
+tint_add_target(tint_lang_wgsl_writer_fuzz fuzz
+)
+
+tint_target_add_dependencies(tint_lang_wgsl_writer_fuzz fuzz
+  tint_lang_core
+  tint_lang_core_constant
+  tint_lang_core_type
+  tint_lang_wgsl
+  tint_lang_wgsl_ast
+  tint_lang_wgsl_common
+  tint_lang_wgsl_features
+  tint_lang_wgsl_program
+  tint_lang_wgsl_sem
+  tint_lang_wgsl_writer_ir_to_program
+  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
+)
+
+if(TINT_BUILD_WGSL_READER)
+  tint_target_add_sources(tint_lang_wgsl_writer_fuzz fuzz
+    "lang/wgsl/writer/writer_fuzz.cc"
+  )
+  tint_target_add_dependencies(tint_lang_wgsl_writer_fuzz fuzz
+    tint_cmd_fuzz_wgsl_fuzz
+  )
+endif(TINT_BUILD_WGSL_READER)
+
+if(TINT_BUILD_WGSL_WRITER)
+  tint_target_add_dependencies(tint_lang_wgsl_writer_fuzz fuzz
+    tint_lang_wgsl_writer
+  )
+endif(TINT_BUILD_WGSL_WRITER)
+
 endif(TINT_BUILD_WGSL_WRITER)
\ No newline at end of file
diff --git a/src/tint/lang/wgsl/writer/BUILD.gn b/src/tint/lang/wgsl/writer/BUILD.gn
index 107e29b..f7afabd 100644
--- a/src/tint/lang/wgsl/writer/BUILD.gn
+++ b/src/tint/lang/wgsl/writer/BUILD.gn
@@ -125,3 +125,43 @@
     }
   }
 }
+if (tint_build_wgsl_writer) {
+  tint_fuzz_source_set("fuzz") {
+    sources = []
+    deps = [
+      "${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/ast",
+      "${tint_src_dir}/lang/wgsl/common",
+      "${tint_src_dir}/lang/wgsl/features",
+      "${tint_src_dir}/lang/wgsl/program",
+      "${tint_src_dir}/lang/wgsl/sem",
+      "${tint_src_dir}/lang/wgsl/writer/ir_to_program",
+      "${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",
+    ]
+
+    if (tint_build_wgsl_reader) {
+      sources += [ "writer_fuzz.cc" ]
+      deps += [ "${tint_src_dir}/cmd/fuzz/wgsl:fuzz" ]
+    }
+
+    if (tint_build_wgsl_writer) {
+      deps += [ "${tint_src_dir}/lang/wgsl/writer" ]
+    }
+  }
+}
diff --git a/src/tint/lang/wgsl/writer/ast_printer/BUILD.bazel b/src/tint/lang/wgsl/writer/ast_printer/BUILD.bazel
index fa89968..f4308d6 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/BUILD.bazel
+++ b/src/tint/lang/wgsl/writer/ast_printer/BUILD.bazel
@@ -146,11 +146,6 @@
 )
 
 alias(
-  name = "tint_build_wgsl_reader",
-  actual = "//src/tint:tint_build_wgsl_reader_true",
-)
-
-alias(
   name = "tint_build_wgsl_writer",
   actual = "//src/tint:tint_build_wgsl_writer_true",
 )
diff --git a/src/tint/lang/wgsl/writer/ast_printer/BUILD.cmake b/src/tint/lang/wgsl/writer/ast_printer/BUILD.cmake
index 11af11b..1f2d210 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/BUILD.cmake
+++ b/src/tint/lang/wgsl/writer/ast_printer/BUILD.cmake
@@ -150,54 +150,4 @@
   )
 endif(TINT_BUILD_WGSL_WRITER)
 
-endif(TINT_BUILD_WGSL_WRITER)
-if(TINT_BUILD_WGSL_WRITER)
-################################################################################
-# Target:    tint_lang_wgsl_writer_ast_printer_fuzz
-# Kind:      fuzz
-# Condition: TINT_BUILD_WGSL_WRITER
-################################################################################
-tint_add_target(tint_lang_wgsl_writer_ast_printer_fuzz fuzz
-)
-
-tint_target_add_dependencies(tint_lang_wgsl_writer_ast_printer_fuzz fuzz
-  tint_lang_core
-  tint_lang_core_constant
-  tint_lang_core_type
-  tint_lang_wgsl
-  tint_lang_wgsl_ast
-  tint_lang_wgsl_program
-  tint_lang_wgsl_sem
-  tint_utils_bytes
-  tint_utils_containers
-  tint_utils_diagnostic
-  tint_utils_generator
-  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
-)
-
-if(TINT_BUILD_WGSL_READER)
-  tint_target_add_sources(tint_lang_wgsl_writer_ast_printer_fuzz fuzz
-    "lang/wgsl/writer/ast_printer/ast_printer_fuzz.cc"
-  )
-  tint_target_add_dependencies(tint_lang_wgsl_writer_ast_printer_fuzz fuzz
-    tint_cmd_fuzz_wgsl_fuzz
-  )
-endif(TINT_BUILD_WGSL_READER)
-
-if(TINT_BUILD_WGSL_WRITER)
-  tint_target_add_dependencies(tint_lang_wgsl_writer_ast_printer_fuzz fuzz
-    tint_lang_wgsl_writer_ast_printer
-  )
-endif(TINT_BUILD_WGSL_WRITER)
-
 endif(TINT_BUILD_WGSL_WRITER)
\ No newline at end of file
diff --git a/src/tint/lang/wgsl/writer/ast_printer/BUILD.gn b/src/tint/lang/wgsl/writer/ast_printer/BUILD.gn
index dd5524f..796fc83 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/BUILD.gn
+++ b/src/tint/lang/wgsl/writer/ast_printer/BUILD.gn
@@ -145,41 +145,3 @@
     }
   }
 }
-if (tint_build_wgsl_writer) {
-  tint_fuzz_source_set("fuzz") {
-    sources = []
-    deps = [
-      "${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/ast",
-      "${tint_src_dir}/lang/wgsl/program",
-      "${tint_src_dir}/lang/wgsl/sem",
-      "${tint_src_dir}/utils/bytes",
-      "${tint_src_dir}/utils/containers",
-      "${tint_src_dir}/utils/diagnostic",
-      "${tint_src_dir}/utils/generator",
-      "${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",
-    ]
-
-    if (tint_build_wgsl_reader) {
-      sources += [ "ast_printer_fuzz.cc" ]
-      deps += [ "${tint_src_dir}/cmd/fuzz/wgsl:fuzz" ]
-    }
-
-    if (tint_build_wgsl_writer) {
-      deps += [ "${tint_src_dir}/lang/wgsl/writer/ast_printer" ]
-    }
-  }
-}
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
index c7df1b6..f7b6321 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program.cc
@@ -104,7 +104,7 @@
     explicit State(const core::ir::Module& m) : mod(m) {}
 
     Program Run(const ProgramOptions& options) {
-        if (auto res = core::ir::Validate(mod); !res) {
+        if (auto res = core::ir::Validate(mod); res != Success) {
             // IR module failed validation.
             b.Diagnostics() = res.Failure().reason;
             return Program{resolver::Resolve(b)};
@@ -211,47 +211,50 @@
             // Emit parameter attributes.
             if (auto builtin = param->Builtin()) {
                 switch (builtin.value()) {
-                    case core::ir::FunctionParam::Builtin::kVertexIndex:
+                    case core::BuiltinValue::kVertexIndex:
                         attrs.Push(b.Builtin(core::BuiltinValue::kVertexIndex));
                         break;
-                    case core::ir::FunctionParam::Builtin::kInstanceIndex:
+                    case core::BuiltinValue::kInstanceIndex:
                         attrs.Push(b.Builtin(core::BuiltinValue::kInstanceIndex));
                         break;
-                    case core::ir::FunctionParam::Builtin::kPosition:
+                    case core::BuiltinValue::kPosition:
                         attrs.Push(b.Builtin(core::BuiltinValue::kPosition));
                         break;
-                    case core::ir::FunctionParam::Builtin::kFrontFacing:
+                    case core::BuiltinValue::kFrontFacing:
                         attrs.Push(b.Builtin(core::BuiltinValue::kFrontFacing));
                         break;
-                    case core::ir::FunctionParam::Builtin::kLocalInvocationId:
+                    case core::BuiltinValue::kLocalInvocationId:
                         attrs.Push(b.Builtin(core::BuiltinValue::kLocalInvocationId));
                         break;
-                    case core::ir::FunctionParam::Builtin::kLocalInvocationIndex:
+                    case core::BuiltinValue::kLocalInvocationIndex:
                         attrs.Push(b.Builtin(core::BuiltinValue::kLocalInvocationIndex));
                         break;
-                    case core::ir::FunctionParam::Builtin::kGlobalInvocationId:
+                    case core::BuiltinValue::kGlobalInvocationId:
                         attrs.Push(b.Builtin(core::BuiltinValue::kGlobalInvocationId));
                         break;
-                    case core::ir::FunctionParam::Builtin::kWorkgroupId:
+                    case core::BuiltinValue::kWorkgroupId:
                         attrs.Push(b.Builtin(core::BuiltinValue::kWorkgroupId));
                         break;
-                    case core::ir::FunctionParam::Builtin::kNumWorkgroups:
+                    case core::BuiltinValue::kNumWorkgroups:
                         attrs.Push(b.Builtin(core::BuiltinValue::kNumWorkgroups));
                         break;
-                    case core::ir::FunctionParam::Builtin::kSampleIndex:
+                    case core::BuiltinValue::kSampleIndex:
                         attrs.Push(b.Builtin(core::BuiltinValue::kSampleIndex));
                         break;
-                    case core::ir::FunctionParam::Builtin::kSampleMask:
+                    case core::BuiltinValue::kSampleMask:
                         attrs.Push(b.Builtin(core::BuiltinValue::kSampleMask));
                         break;
-                    case core::ir::FunctionParam::Builtin::kSubgroupInvocationId:
+                    case core::BuiltinValue::kSubgroupInvocationId:
                         Enable(wgsl::Extension::kChromiumExperimentalSubgroups);
                         attrs.Push(b.Builtin(core::BuiltinValue::kSubgroupInvocationId));
                         break;
-                    case core::ir::FunctionParam::Builtin::kSubgroupSize:
+                    case core::BuiltinValue::kSubgroupSize:
                         Enable(wgsl::Extension::kChromiumExperimentalSubgroups);
                         attrs.Push(b.Builtin(core::BuiltinValue::kSubgroupSize));
                         break;
+                    default:
+                        TINT_UNIMPLEMENTED() << builtin.value();
+                        break;
                 }
             }
             if (auto loc = param->Location()) {
@@ -297,15 +300,18 @@
         // Emit return type attributes.
         if (auto builtin = fn->ReturnBuiltin()) {
             switch (builtin.value()) {
-                case core::ir::Function::ReturnBuiltin::kPosition:
+                case core::BuiltinValue::kPosition:
                     ret_attrs.Push(b.Builtin(core::BuiltinValue::kPosition));
                     break;
-                case core::ir::Function::ReturnBuiltin::kFragDepth:
+                case core::BuiltinValue::kFragDepth:
                     ret_attrs.Push(b.Builtin(core::BuiltinValue::kFragDepth));
                     break;
-                case core::ir::Function::ReturnBuiltin::kSampleMask:
+                case core::BuiltinValue::kSampleMask:
                     ret_attrs.Push(b.Builtin(core::BuiltinValue::kSampleMask));
                     break;
+                default:
+                    TINT_UNIMPLEMENTED() << builtin.value();
+                    break;
             }
         }
         if (auto loc = fn->ReturnLocation()) {
diff --git a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
index 844aa4e..f379020 100644
--- a/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
+++ b/src/tint/lang/wgsl/writer/ir_to_program/ir_to_program_test.cc
@@ -58,7 +58,7 @@
     }
 
     auto output = wgsl::writer::Generate(output_program, {});
-    if (!output) {
+    if (output != Success) {
         std::stringstream ss;
         ss << "wgsl::Generate() errored: " << output.Failure();
         result.err = ss.str();
@@ -144,7 +144,7 @@
 
 TEST_F(IRToProgramTest, EntryPoint_Vertex) {
     auto* fn = b.Function("f", ty.vec4<f32>(), core::ir::Function::PipelineStage::kVertex);
-    fn->SetReturnBuiltin(core::ir::Function::ReturnBuiltin::kPosition);
+    fn->SetReturnBuiltin(core::BuiltinValue::kPosition);
 
     fn->Block()->Append(b.Return(fn, b.Splat(ty.vec4<f32>(), 0_f, 4)));
 
@@ -158,7 +158,7 @@
 
 TEST_F(IRToProgramTest, EntryPoint_ReturnAttribute_FragDepth) {
     auto* fn = b.Function("f", ty.f32(), core::ir::Function::PipelineStage::kFragment);
-    fn->SetReturnBuiltin(core::ir::Function::ReturnBuiltin::kFragDepth);
+    fn->SetReturnBuiltin(core::BuiltinValue::kFragDepth);
 
     fn->Block()->Append(b.Return(fn, b.Constant(0.5_f)));
 
@@ -172,7 +172,7 @@
 
 TEST_F(IRToProgramTest, EntryPoint_ReturnAttribute_SampleMask) {
     auto* fn = b.Function("f", ty.u32(), core::ir::Function::PipelineStage::kFragment);
-    fn->SetReturnBuiltin(core::ir::Function::ReturnBuiltin::kSampleMask);
+    fn->SetReturnBuiltin(core::BuiltinValue::kSampleMask);
 
     fn->Block()->Append(b.Return(fn, b.Constant(3_u)));
 
@@ -186,7 +186,7 @@
 
 TEST_F(IRToProgramTest, EntryPoint_ReturnAttribute_Invariant) {
     auto* fn = b.Function("f", ty.vec4<f32>(), core::ir::Function::PipelineStage::kVertex);
-    fn->SetReturnBuiltin(core::ir::Function::ReturnBuiltin::kPosition);
+    fn->SetReturnBuiltin(core::BuiltinValue::kPosition);
     fn->SetReturnInvariant(true);
 
     fn->Block()->Append(b.Return(fn, b.Splat(ty.vec4<f32>(), 0_f, 4)));
@@ -216,7 +216,7 @@
 namespace {
 core::ir::FunctionParam* MakeBuiltinParam(core::ir::Builder& b,
                                           const core::type::Type* type,
-                                          enum core::ir::FunctionParam::Builtin builtin) {
+                                          enum core::BuiltinValue builtin) {
     auto* param = b.FunctionParam(type);
     param->SetBuiltin(builtin);
     return param;
@@ -227,13 +227,13 @@
     auto* fn = b.Function("f", ty.void_(), core::ir::Function::PipelineStage::kCompute,
                           std::array{3u, 4u, 5u});
     fn->SetParams({
-        MakeBuiltinParam(b, ty.vec3<u32>(), core::ir::FunctionParam::Builtin::kLocalInvocationId),
-        MakeBuiltinParam(b, ty.u32(), core::ir::FunctionParam::Builtin::kLocalInvocationIndex),
-        MakeBuiltinParam(b, ty.vec3<u32>(), core::ir::FunctionParam::Builtin::kGlobalInvocationId),
-        MakeBuiltinParam(b, ty.vec3<u32>(), core::ir::FunctionParam::Builtin::kWorkgroupId),
-        MakeBuiltinParam(b, ty.vec3<u32>(), core::ir::FunctionParam::Builtin::kNumWorkgroups),
-        MakeBuiltinParam(b, ty.u32(), core::ir::FunctionParam::Builtin::kSubgroupInvocationId),
-        MakeBuiltinParam(b, ty.u32(), core::ir::FunctionParam::Builtin::kSubgroupSize),
+        MakeBuiltinParam(b, ty.vec3<u32>(), core::BuiltinValue::kLocalInvocationId),
+        MakeBuiltinParam(b, ty.u32(), core::BuiltinValue::kLocalInvocationIndex),
+        MakeBuiltinParam(b, ty.vec3<u32>(), core::BuiltinValue::kGlobalInvocationId),
+        MakeBuiltinParam(b, ty.vec3<u32>(), core::BuiltinValue::kWorkgroupId),
+        MakeBuiltinParam(b, ty.vec3<u32>(), core::BuiltinValue::kNumWorkgroups),
+        MakeBuiltinParam(b, ty.u32(), core::BuiltinValue::kSubgroupInvocationId),
+        MakeBuiltinParam(b, ty.u32(), core::BuiltinValue::kSubgroupSize),
     });
 
     fn->Block()->Append(b.Return(fn));
@@ -250,9 +250,9 @@
 TEST_F(IRToProgramTest, EntryPoint_ParameterAttribute_Fragment) {
     auto* fn = b.Function("f", ty.void_(), core::ir::Function::PipelineStage::kFragment);
     fn->SetParams({
-        MakeBuiltinParam(b, ty.bool_(), core::ir::FunctionParam::Builtin::kFrontFacing),
-        MakeBuiltinParam(b, ty.u32(), core::ir::FunctionParam::Builtin::kSampleIndex),
-        MakeBuiltinParam(b, ty.u32(), core::ir::FunctionParam::Builtin::kSampleMask),
+        MakeBuiltinParam(b, ty.bool_(), core::BuiltinValue::kFrontFacing),
+        MakeBuiltinParam(b, ty.u32(), core::BuiltinValue::kSampleIndex),
+        MakeBuiltinParam(b, ty.u32(), core::BuiltinValue::kSampleMask),
     });
 
     fn->Block()->Append(b.Return(fn));
diff --git a/src/tint/lang/wgsl/writer/raise/raise.cc b/src/tint/lang/wgsl/writer/raise/raise.cc
index cf0f06e..9d69c41 100644
--- a/src/tint/lang/wgsl/writer/raise/raise.cc
+++ b/src/tint/lang/wgsl/writer/raise/raise.cc
@@ -229,7 +229,7 @@
             }
         }
     }
-    if (auto result = RenameConflicts(&mod); !result) {
+    if (auto result = RenameConflicts(&mod); result != Success) {
         return result.Failure();
     }
 
diff --git a/src/tint/lang/wgsl/writer/raise/rename_conflicts.cc b/src/tint/lang/wgsl/writer/raise/rename_conflicts.cc
index 7636059..8ab6b7e 100644
--- a/src/tint/lang/wgsl/writer/raise/rename_conflicts.cc
+++ b/src/tint/lang/wgsl/writer/raise/rename_conflicts.cc
@@ -285,7 +285,7 @@
 
 Result<SuccessType> RenameConflicts(core::ir::Module* ir) {
     auto result = ValidateAndDumpIfNeeded(*ir, "RenameConflicts transform");
-    if (!result) {
+    if (result != Success) {
         return result;
     }
 
diff --git a/src/tint/lang/wgsl/writer/raise/rename_conflicts_test.cc b/src/tint/lang/wgsl/writer/raise/rename_conflicts_test.cc
index bc46235..6facee7 100644
--- a/src/tint/lang/wgsl/writer/raise/rename_conflicts_test.cc
+++ b/src/tint/lang/wgsl/writer/raise/rename_conflicts_test.cc
@@ -49,19 +49,19 @@
         // Validate the input IR.
         {
             auto res = core::ir::Validate(mod);
-            EXPECT_TRUE(res) << res.Failure().reason.str();
-            if (!res) {
+            EXPECT_EQ(res, Success);
+            if (res != Success) {
                 return;
             }
         }
 
         // Run the transforms.
         auto result = RenameConflicts(&mod);
-        EXPECT_TRUE(result) << result.Failure();
+        EXPECT_EQ(result, Success);
 
         // Validate the output IR.
         auto res = core::ir::Validate(mod);
-        EXPECT_TRUE(res) << res.Failure().reason.str();
+        EXPECT_EQ(res, Success);
     }
 
     /// @returns the transformed module as a disassembled string
@@ -202,7 +202,7 @@
         b.Append(fn->Block(), [&] {
             auto* load_outer = b.Load(outer);
 
-            auto* inner = b.Var(ty.ptr<function, f32>());
+            auto* inner = b.Var(ty.ptr<function, i32>());
             b.ir.SetName(inner, "v");
 
             auto* load_inner = b.Load(inner);
@@ -218,8 +218,8 @@
 %f = func():i32 -> %b2 {
   %b2 = block {
     %3:i32 = load %v
-    %v_1:ptr<function, f32, read_write> = var  # %v_1: 'v'
-    %5:f32 = load %v_1
+    %v_1:ptr<function, i32, read_write> = var  # %v_1: 'v'
+    %5:i32 = load %v_1
     %6:i32 = add %3, %5
     ret %6
   }
@@ -241,7 +241,7 @@
 
         auto* fn = b.Function("f", ty.i32());
         b.Append(fn->Block(), [&] {
-            auto* inner = b.Var(ty.ptr<function, f32>());
+            auto* inner = b.Var(ty.ptr<function, i32>());
             b.ir.SetName(inner, "v");
 
             auto* load_outer = b.Load(outer);
@@ -257,9 +257,9 @@
 
 %f = func():i32 -> %b2 {
   %b2 = block {
-    %v_1:ptr<function, f32, read_write> = var  # %v_1: 'v'
+    %v_1:ptr<function, i32, read_write> = var  # %v_1: 'v'
     %4:i32 = load %v
-    %5:f32 = load %v_1
+    %5:i32 = load %v_1
     %6:i32 = add %4, %5
     ret %6
   }
@@ -274,9 +274,9 @@
 
 %f = func():i32 -> %b2 {
   %b2 = block {
-    %v_1:ptr<function, f32, read_write> = var
+    %v_1:ptr<function, i32, read_write> = var
     %4:i32 = load %v
-    %5:f32 = load %v_1
+    %5:i32 = load %v_1
     %6:i32 = add %4, %5
     ret %6
   }
@@ -291,14 +291,14 @@
 TEST_F(IRToProgramRenameConflictsTest, NoModify_FnVar_ShadowedBy_IfVar) {
     auto* fn = b.Function("f", ty.i32());
     b.Append(fn->Block(), [&] {
-        auto* outer = b.Var(ty.ptr<function, f32>());
+        auto* outer = b.Var(ty.ptr<function, i32>());
         b.ir.SetName(outer, "v");
 
         auto* if_ = b.If(true);
         b.Append(if_->True(), [&] {
             auto* load_outer = b.Load(outer);
 
-            auto* inner = b.Var(ty.ptr<function, f32>());
+            auto* inner = b.Var(ty.ptr<function, i32>());
             b.ir.SetName(inner, "v");
 
             auto* load_inner = b.Load(inner);
@@ -311,12 +311,12 @@
     auto* src = R"(
 %f = func():i32 -> %b1 {
   %b1 = block {
-    %v:ptr<function, f32, read_write> = var
+    %v:ptr<function, i32, read_write> = var
     if true [t: %b2] {  # if_1
       %b2 = block {  # true
-        %3:f32 = load %v
-        %v_1:ptr<function, f32, read_write> = var  # %v_1: 'v'
-        %5:f32 = load %v_1
+        %3:i32 = load %v
+        %v_1:ptr<function, i32, read_write> = var  # %v_1: 'v'
+        %5:i32 = load %v_1
         %6:i32 = add %3, %5
         ret %6
       }
@@ -337,12 +337,12 @@
 TEST_F(IRToProgramRenameConflictsTest, Conflict_FnVar_ShadowedBy_IfVar) {
     auto* fn = b.Function("f", ty.i32());
     b.Append(fn->Block(), [&] {
-        auto* outer = b.Var(ty.ptr<function, f32>());
+        auto* outer = b.Var(ty.ptr<function, i32>());
         b.ir.SetName(outer, "v");
 
         auto* if_ = b.If(true);
         b.Append(if_->True(), [&] {
-            auto* inner = b.Var(ty.ptr<function, f32>());
+            auto* inner = b.Var(ty.ptr<function, i32>());
             b.ir.SetName(inner, "v");
 
             auto* load_outer = b.Load(outer);
@@ -356,12 +356,12 @@
     auto* src = R"(
 %f = func():i32 -> %b1 {
   %b1 = block {
-    %v:ptr<function, f32, read_write> = var
+    %v:ptr<function, i32, read_write> = var
     if true [t: %b2] {  # if_1
       %b2 = block {  # true
-        %v_1:ptr<function, f32, read_write> = var  # %v_1: 'v'
-        %4:f32 = load %v
-        %5:f32 = load %v_1
+        %v_1:ptr<function, i32, read_write> = var  # %v_1: 'v'
+        %4:i32 = load %v
+        %5:i32 = load %v_1
         %6:i32 = add %4, %5
         ret %6
       }
@@ -375,12 +375,12 @@
     auto* expect = R"(
 %f = func():i32 -> %b1 {
   %b1 = block {
-    %v:ptr<function, f32, read_write> = var
+    %v:ptr<function, i32, read_write> = var
     if true [t: %b2] {  # if_1
       %b2 = block {  # true
-        %v_1:ptr<function, f32, read_write> = var
-        %4:f32 = load %v
-        %5:f32 = load %v_1
+        %v_1:ptr<function, i32, read_write> = var
+        %4:i32 = load %v
+        %5:i32 = load %v_1
         %6:i32 = add %4, %5
         ret %6
       }
@@ -400,14 +400,14 @@
     b.Append(fn->Block(), [&] {
         auto* loop = b.Loop();
         b.Append(loop->Initializer(), [&] {
-            auto* outer = b.Var(ty.ptr<function, f32>());
+            auto* outer = b.Var(ty.ptr<function, i32>());
             b.ir.SetName(outer, "v");
             b.NextIteration(loop);
 
             b.Append(loop->Body(), [&] {
                 auto* load_outer = b.Load(outer);
 
-                auto* inner = b.Var(ty.ptr<function, f32>());
+                auto* inner = b.Var(ty.ptr<function, i32>());
                 b.ir.SetName(inner, "v");
 
                 auto* load_inner = b.Load(inner);
@@ -423,13 +423,13 @@
   %b1 = block {
     loop [i: %b2, b: %b3] {  # loop_1
       %b2 = block {  # initializer
-        %v:ptr<function, f32, read_write> = var
+        %v:ptr<function, i32, read_write> = var
         next_iteration %b3
       }
       %b3 = block {  # body
-        %3:f32 = load %v
-        %v_1:ptr<function, f32, read_write> = var  # %v_1: 'v'
-        %5:f32 = load %v_1
+        %3:i32 = load %v
+        %v_1:ptr<function, i32, read_write> = var  # %v_1: 'v'
+        %5:i32 = load %v_1
         %6:i32 = add %3, %5
         ret %6
       }
@@ -452,12 +452,12 @@
     b.Append(fn->Block(), [&] {
         auto* loop = b.Loop();
         b.Append(loop->Initializer(), [&] {
-            auto* outer = b.Var(ty.ptr<function, f32>());
+            auto* outer = b.Var(ty.ptr<function, i32>());
             b.ir.SetName(outer, "v");
             b.NextIteration(loop);
 
             b.Append(loop->Body(), [&] {
-                auto* inner = b.Var(ty.ptr<function, f32>());
+                auto* inner = b.Var(ty.ptr<function, i32>());
                 b.ir.SetName(inner, "v");
 
                 auto* load_outer = b.Load(outer);
@@ -474,13 +474,13 @@
   %b1 = block {
     loop [i: %b2, b: %b3] {  # loop_1
       %b2 = block {  # initializer
-        %v:ptr<function, f32, read_write> = var
+        %v:ptr<function, i32, read_write> = var
         next_iteration %b3
       }
       %b3 = block {  # body
-        %v_1:ptr<function, f32, read_write> = var  # %v_1: 'v'
-        %4:f32 = load %v
-        %5:f32 = load %v_1
+        %v_1:ptr<function, i32, read_write> = var  # %v_1: 'v'
+        %4:i32 = load %v
+        %5:i32 = load %v_1
         %6:i32 = add %4, %5
         ret %6
       }
@@ -496,13 +496,13 @@
   %b1 = block {
     loop [i: %b2, b: %b3] {  # loop_1
       %b2 = block {  # initializer
-        %v:ptr<function, f32, read_write> = var
+        %v:ptr<function, i32, read_write> = var
         next_iteration %b3
       }
       %b3 = block {  # body
-        %v_1:ptr<function, f32, read_write> = var
-        %4:f32 = load %v
-        %5:f32 = load %v_1
+        %v_1:ptr<function, i32, read_write> = var
+        %4:i32 = load %v
+        %5:i32 = load %v_1
         %6:i32 = add %4, %5
         ret %6
       }
@@ -523,14 +523,14 @@
         auto* loop = b.Loop();
         b.Append(loop->Initializer(), [&] { b.NextIteration(loop); });
         b.Append(loop->Body(), [&] {
-            auto* outer = b.Var(ty.ptr<function, f32>());
+            auto* outer = b.Var(ty.ptr<function, i32>());
             b.ir.SetName(outer, "v");
             b.Continue(loop);
 
             b.Append(loop->Continuing(), [&] {
                 auto* load_outer = b.Load(outer);
 
-                auto* inner = b.Var(ty.ptr<function, f32>());
+                auto* inner = b.Var(ty.ptr<function, i32>());
                 b.ir.SetName(inner, "v");
 
                 auto* load_inner = b.Load(inner);
@@ -549,13 +549,13 @@
         next_iteration %b3
       }
       %b3 = block {  # body
-        %v:ptr<function, f32, read_write> = var
+        %v:ptr<function, i32, read_write> = var
         continue %b4
       }
       %b4 = block {  # continuing
-        %3:f32 = load %v
-        %v_1:ptr<function, f32, read_write> = var  # %v_1: 'v'
-        %5:f32 = load %v_1
+        %3:i32 = load %v
+        %v_1:ptr<function, i32, read_write> = var  # %v_1: 'v'
+        %5:i32 = load %v_1
         %6:i32 = add %3, %5
         ret %6
       }
@@ -579,12 +579,12 @@
         auto* loop = b.Loop();
         b.Append(loop->Initializer(), [&] { b.NextIteration(loop); });
         b.Append(loop->Body(), [&] {
-            auto* outer = b.Var(ty.ptr<function, f32>());
+            auto* outer = b.Var(ty.ptr<function, i32>());
             b.ir.SetName(outer, "v");
             b.Continue(loop);
 
             b.Append(loop->Continuing(), [&] {
-                auto* inner = b.Var(ty.ptr<function, f32>());
+                auto* inner = b.Var(ty.ptr<function, i32>());
                 b.ir.SetName(inner, "v");
 
                 auto* load_outer = b.Load(outer);
@@ -604,13 +604,13 @@
         next_iteration %b3
       }
       %b3 = block {  # body
-        %v:ptr<function, f32, read_write> = var
+        %v:ptr<function, i32, read_write> = var
         continue %b4
       }
       %b4 = block {  # continuing
-        %v_1:ptr<function, f32, read_write> = var  # %v_1: 'v'
-        %4:f32 = load %v
-        %5:f32 = load %v_1
+        %v_1:ptr<function, i32, read_write> = var  # %v_1: 'v'
+        %4:i32 = load %v
+        %5:i32 = load %v_1
         %6:i32 = add %4, %5
         ret %6
       }
@@ -629,13 +629,13 @@
         next_iteration %b3
       }
       %b3 = block {  # body
-        %v:ptr<function, f32, read_write> = var
+        %v:ptr<function, i32, read_write> = var
         continue %b4
       }
       %b4 = block {  # continuing
-        %v_1:ptr<function, f32, read_write> = var
-        %4:f32 = load %v
-        %5:f32 = load %v_1
+        %v_1:ptr<function, i32, read_write> = var
+        %4:i32 = load %v
+        %5:i32 = load %v_1
         %6:i32 = add %4, %5
         ret %6
       }
diff --git a/src/tint/lang/wgsl/writer/writer.cc b/src/tint/lang/wgsl/writer/writer.cc
index 6cc8422..92819c0 100644
--- a/src/tint/lang/wgsl/writer/writer.cc
+++ b/src/tint/lang/wgsl/writer/writer.cc
@@ -69,7 +69,7 @@
 
 Result<Output> WgslFromIR(core::ir::Module& module, const ProgramOptions& options) {
     // core-dialect -> WGSL-dialect
-    if (auto res = Raise(module); !res) {
+    if (auto res = Raise(module); res != Success) {
         return res.Failure();
     }
 
diff --git a/src/tint/lang/wgsl/writer/writer.h b/src/tint/lang/wgsl/writer/writer.h
index 64a1525..604ace2 100644
--- a/src/tint/lang/wgsl/writer/writer.h
+++ b/src/tint/lang/wgsl/writer/writer.h
@@ -28,12 +28,9 @@
 #ifndef SRC_TINT_LANG_WGSL_WRITER_WRITER_H_
 #define SRC_TINT_LANG_WGSL_WRITER_WRITER_H_
 
-#include <string>
-
 #include "src/tint/lang/wgsl/writer/ir_to_program/program_options.h"
 #include "src/tint/lang/wgsl/writer/options.h"
 #include "src/tint/lang/wgsl/writer/output.h"
-#include "src/tint/utils/diagnostic/diagnostic.h"
 #include "src/tint/utils/result/result.h"
 
 // Forward declarations
diff --git a/src/tint/lang/wgsl/writer/writer_bench.cc b/src/tint/lang/wgsl/writer/writer_bench.cc
index bd82cb7..68dc9a2 100644
--- a/src/tint/lang/wgsl/writer/writer_bench.cc
+++ b/src/tint/lang/wgsl/writer/writer_bench.cc
@@ -35,13 +35,13 @@
 
 void GenerateWGSL(benchmark::State& state, std::string input_name) {
     auto res = bench::LoadProgram(input_name);
-    if (!res) {
+    if (res != Success) {
         state.SkipWithError(res.Failure().reason.str());
         return;
     }
     for (auto _ : state) {
         auto gen_res = Generate(res->program, {});
-        if (!gen_res) {
+        if (gen_res != Success) {
             state.SkipWithError(gen_res.Failure().reason.str());
         }
     }
diff --git a/src/tint/lang/wgsl/writer/ast_printer/ast_printer_fuzz.cc b/src/tint/lang/wgsl/writer/writer_fuzz.cc
similarity index 85%
rename from src/tint/lang/wgsl/writer/ast_printer/ast_printer_fuzz.cc
rename to src/tint/lang/wgsl/writer/writer_fuzz.cc
index 230ab20..4314774 100644
--- a/src/tint/lang/wgsl/writer/ast_printer/ast_printer_fuzz.cc
+++ b/src/tint/lang/wgsl/writer/writer_fuzz.cc
@@ -1,4 +1,4 @@
-// Copyright 2021 The Dawn & Tint Authors
+// 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:
@@ -27,18 +27,18 @@
 
 // GEN_BUILD:CONDITION(tint_build_wgsl_reader)
 
-#include "src/tint/lang/wgsl/writer/ast_printer/ast_printer.h"
+#include "src/tint/lang/wgsl/writer/writer.h"
 
 #include "src/tint/cmd/fuzz/wgsl/fuzz.h"
 
 namespace tint::wgsl::writer {
 namespace {
 
-void ASTPrinterFuzzer(const tint::Program& program) {
-    ASTPrinter{program}.Generate();
+void WriterFuzzer(const tint::Program& program) {
+    [[maybe_unused]] auto res = tint::wgsl::writer::Generate(program, {});
 }
 
 }  // namespace
 }  // namespace tint::wgsl::writer
 
-TINT_WGSL_PROGRAM_FUZZER(tint::wgsl::writer::ASTPrinterFuzzer);
+TINT_WGSL_PROGRAM_FUZZER(tint::wgsl::writer::WriterFuzzer);
diff --git a/src/tint/utils/bytes/buffer_writer_test.cc b/src/tint/utils/bytes/buffer_writer_test.cc
index 7862e28..7d386ca 100644
--- a/src/tint/utils/bytes/buffer_writer_test.cc
+++ b/src/tint/utils/bytes/buffer_writer_test.cc
@@ -40,25 +40,25 @@
 
 TEST(BufferWriterTest, IntegerBigEndian) {
     BufferWriter<16> writer;
-    EXPECT_TRUE(writer.Int(0x10203040u, Endianness::kBig));
+    EXPECT_EQ(writer.Int(0x10203040u, Endianness::kBig), Success);
     EXPECT_THAT(Cast<int>(writer.buffer), testing::ElementsAre(0x10, 0x20, 0x30, 0x40));
 }
 
 TEST(BufferWriterTest, IntegerLittleEndian) {
     BufferWriter<16> writer;
-    EXPECT_TRUE(writer.Int(0x10203040u, Endianness::kLittle));
+    EXPECT_EQ(writer.Int(0x10203040u, Endianness::kLittle), Success);
     EXPECT_THAT(Cast<int>(writer.buffer), testing::ElementsAre(0x40, 0x30, 0x20, 0x10));
 }
 
 TEST(BufferWriterTest, Float) {
     BufferWriter<16> writer;
-    EXPECT_TRUE(writer.Float<float>(8.5f));
+    EXPECT_EQ(writer.Float<float>(8.5f), Success);
     EXPECT_THAT(Cast<int>(writer.buffer), testing::ElementsAre(0x00, 0x00, 0x08, 0x41));
 }
 
 TEST(BufferWriterTest, Bool) {
     BufferWriter<16> writer;
-    EXPECT_TRUE(writer.Bool(true));
+    EXPECT_EQ(writer.Bool(true), Success);
     EXPECT_THAT(Cast<int>(writer.buffer), testing::ElementsAre(0x01));
 }
 
diff --git a/src/tint/utils/bytes/decoder.h b/src/tint/utils/bytes/decoder.h
index b107497..95e9f8f 100644
--- a/src/tint/utils/bytes/decoder.h
+++ b/src/tint/utils/bytes/decoder.h
@@ -28,6 +28,9 @@
 #ifndef SRC_TINT_UTILS_BYTES_DECODER_H_
 #define SRC_TINT_UTILS_BYTES_DECODER_H_
 
+#include <bitset>
+#include <climits>
+#include <optional>
 #include <string>
 #include <tuple>
 #include <unordered_map>
@@ -79,7 +82,7 @@
     /// @returns the decoded string, or an error if the stream is too short.
     static Result<std::string> Decode(Reader& reader) {
         auto len = reader.Int<uint16_t>();
-        if (!len) {
+        if (len != Success) {
             return len.Failure();
         }
         return reader.String(len.Get());
@@ -106,7 +109,7 @@
         diag::List errs;
         ForeachField(object, [&](auto& field) {  //
             auto value = bytes::Decode<std::decay_t<decltype(field)>>(reader);
-            if (value) {
+            if (value == Success) {
                 field = value.Get();
             } else {
                 errs.add(value.Failure().reason);
@@ -130,18 +133,18 @@
 
         while (true) {
             auto stop = bytes::Decode<bool>(reader);
-            if (!stop) {
+            if (stop != Success) {
                 return stop.Failure();
             }
             if (stop.Get()) {
                 break;
             }
             auto key = bytes::Decode<K>(reader);
-            if (!key) {
+            if (key != Success) {
                 return key.Failure();
             }
             auto val = bytes::Decode<V>(reader);
-            if (!val) {
+            if (val != Success) {
                 return val.Failure();
             }
             out.emplace(std::move(key.Get()), std::move(val.Get()));
@@ -151,6 +154,52 @@
     }
 };
 
+/// Decoder specialization for std::optional
+template <typename T>
+struct Decoder<std::optional<T>, void> {
+    /// Decode decodes the optional from @p reader.
+    /// @param reader the reader to decode from
+    /// @returns the decoded optional, or an error if the stream is too short.
+    static Result<std::optional<T>> Decode(Reader& reader) {
+        auto has_value = bytes::Decode<bool>(reader);
+        if (has_value != Success) {
+            return has_value.Failure();
+        }
+        if (!has_value.Get()) {
+            return std::optional<T>{std::nullopt};
+        }
+        auto value = bytes::Decode<T>(reader);
+        if (value != Success) {
+            return value.Failure();
+        }
+        return std::optional<T>{value.Get()};
+    }
+};
+
+/// Decoder specialization for std::bitset
+template <std::size_t N>
+struct Decoder<std::bitset<N>, void> {
+    /// Decode decodes the bitset from @p reader.
+    /// @param reader the reader to decode from
+    /// @returns the decoded bitset, or an error if the stream is too short.
+    static Result<std::bitset<N>> Decode(Reader& reader) {
+        Vector<std::byte, 32> vec;
+        vec.Resize((N + CHAR_BIT - 1) / CHAR_BIT);
+
+        if (auto len = reader.Read(&vec[0], vec.Length()); len != vec.Length()) {
+            return Failure{"EOF"};
+        }
+
+        std::bitset<N> out;
+        for (std::size_t i = 0; i < N; i++) {
+            std::size_t w = i / CHAR_BIT;
+            std::size_t b = i - (w * CHAR_BIT);
+            out[i] = ((vec[w] >> b) & std::byte{1}) != std::byte{0};
+        }
+        return out;
+    }
+};
+
 /// Decoder specialization for std::tuple
 template <typename FIRST, typename... OTHERS>
 struct Decoder<std::tuple<FIRST, OTHERS...>, void> {
@@ -159,12 +208,12 @@
     /// @returns the decoded tuple, or an error if the stream is too short.
     static Result<std::tuple<FIRST, OTHERS...>> Decode(Reader& reader) {
         auto first = bytes::Decode<FIRST>(reader);
-        if (!first) {
+        if (first != Success) {
             return first.Failure();
         }
         if constexpr (sizeof...(OTHERS) > 0) {
             auto others = bytes::Decode<std::tuple<OTHERS...>>(reader);
-            if (!others) {
+            if (others != Success) {
                 return others.Failure();
             }
             return std::tuple_cat(std::tuple<FIRST>(first.Get()), others.Get());
@@ -174,6 +223,29 @@
     }
 };
 
+/// Decoder specialization for enum types that have a range defined with TINT_REFLECT_ENUM_RANGE
+template <typename T>
+struct Decoder<T, std::void_t<decltype(tint::EnumRange<T>::kMax)>> {
+    /// Decode decodes the enum type from @p reader.
+    /// @param reader the reader to decode from
+    /// @param endianness the endianness of the enum
+    /// @returns the decoded enum type, or an error if the stream is too short.
+    static Result<T> Decode(Reader& reader, Endianness endianness = Endianness::kLittle) {
+        using Range = tint::EnumRange<T>;
+        using U = std::underlying_type_t<T>;
+        auto value = reader.Int<U>(endianness);
+        if (value != Success) {
+            return value.Failure();
+        }
+        static constexpr U kMin = static_cast<U>(Range::kMin);
+        static constexpr U kMax = static_cast<U>(Range::kMax);
+        if (value.Get() < kMin || value.Get() > kMax) {
+            return Failure{"value " + std::to_string(value.Get()) + " out of range for enum"};
+        }
+        return static_cast<T>(value.Get());
+    }
+};
+
 }  // namespace tint::bytes
 
 #endif  // SRC_TINT_UTILS_BYTES_DECODER_H_
diff --git a/src/tint/utils/bytes/decoder_test.cc b/src/tint/utils/bytes/decoder_test.cc
index 12640b1..9bae945 100644
--- a/src/tint/utils/bytes/decoder_test.cc
+++ b/src/tint/utils/bytes/decoder_test.cc
@@ -34,9 +34,19 @@
 
 #include "gmock/gmock.h"
 
+namespace tint {
+namespace {
+/// An enum used for decoder tests below.
+enum class TestEnum : uint8_t { A = 2, B = 3, C = 4 };
+}  // namespace
+
+/// Reflect valid value ranges for the TestEnum enum.
+TINT_REFLECT_ENUM_RANGE(TestEnum, A, C);
+
+}  // namespace tint
+
 namespace tint::bytes {
 namespace {
-
 template <typename... ARGS>
 auto Data(ARGS&&... args) {
     return std::array{std::byte{static_cast<uint8_t>(args)}...};
@@ -53,7 +63,7 @@
     EXPECT_EQ(Decode<uint8_t>(reader).Get(), 0x60u);
     EXPECT_EQ(Decode<uint8_t>(reader).Get(), 0x70u);
     EXPECT_EQ(Decode<uint8_t>(reader).Get(), 0x80u);
-    EXPECT_FALSE(Decode<uint8_t>(reader));
+    EXPECT_NE(Decode<uint8_t>(reader), Success);
 }
 
 TEST(BytesDecoderTest, Uint16) {
@@ -63,7 +73,7 @@
     EXPECT_EQ(Decode<uint16_t>(reader).Get(), 0x4030u);
     EXPECT_EQ(Decode<uint16_t>(reader).Get(), 0x6050u);
     EXPECT_EQ(Decode<uint16_t>(reader).Get(), 0x8070u);
-    EXPECT_FALSE(Decode<uint16_t>(reader));
+    EXPECT_NE(Decode<uint16_t>(reader), Success);
 }
 
 TEST(BytesDecoderTest, Uint32) {
@@ -71,14 +81,14 @@
     auto reader = BufferReader{Slice{data}};
     EXPECT_EQ(Decode<uint32_t>(reader, Endianness::kBig).Get(), 0x10203040u);
     EXPECT_EQ(Decode<uint32_t>(reader, Endianness::kBig).Get(), 0x50607080u);
-    EXPECT_FALSE(Decode<uint32_t>(reader));
+    EXPECT_NE(Decode<uint32_t>(reader), Success);
 }
 
 TEST(BytesDecoderTest, Float) {
     auto data = Data(0x00, 0x00, 0x08, 0x41);
     auto reader = BufferReader{Slice{data}};
     EXPECT_EQ(Decode<float>(reader).Get(), 8.5f);
-    EXPECT_FALSE(Decode<float>(reader));
+    EXPECT_NE(Decode<float>(reader), Success);
 }
 
 TEST(BytesDecoderTest, Bool) {
@@ -89,7 +99,7 @@
     EXPECT_EQ(Decode<bool>(reader).Get(), true);
     EXPECT_EQ(Decode<bool>(reader).Get(), true);
     EXPECT_EQ(Decode<bool>(reader).Get(), false);
-    EXPECT_FALSE(Decode<bool>(reader));
+    EXPECT_NE(Decode<bool>(reader), Success);
 }
 
 TEST(BytesDecoderTest, String) {
@@ -97,7 +107,7 @@
     auto reader = BufferReader{Slice{data}};
     EXPECT_EQ(Decode<std::string>(reader).Get(), "hello");
     EXPECT_EQ(Decode<std::string>(reader).Get(), "world");
-    EXPECT_FALSE(Decode<std::string>(reader));
+    EXPECT_NE(Decode<std::string>(reader), Success);
 }
 
 struct S {
@@ -114,7 +124,16 @@
     EXPECT_EQ(got->a, 0x10u);
     EXPECT_EQ(got->b, 0x3020u);
     EXPECT_EQ(got->c, 0x70605040u);
-    EXPECT_FALSE(Decode<S>(reader));
+    EXPECT_NE(Decode<S>(reader), Success);
+}
+
+TEST(BytesDecoderTest, ReflectedEnum) {
+    auto data = Data(0x03, 0x01, 0x05);
+    auto reader = BufferReader{Slice{data}};
+    auto got = Decode<TestEnum>(reader);
+    EXPECT_EQ(got, TestEnum::B);
+    EXPECT_NE(Decode<S>(reader), Success);  // Out of range
+    EXPECT_NE(Decode<S>(reader), Success);  // Out of range
 }
 
 TEST(BytesDecoderTest, UnorderedMap) {
@@ -132,7 +151,28 @@
                                std::pair<uint8_t, uint32_t>(0x50u, 0x6006u),
                                std::pair<uint8_t, uint32_t>(0x70u, 0x8008u),
                            }));
-    EXPECT_FALSE(Decode<M>(reader));
+    EXPECT_NE(Decode<M>(reader), Success);
+}
+
+TEST(BytesDecoderTest, Optional) {
+    auto data = Data(0x00,  //
+                     0x01, 0x42);
+    auto reader = BufferReader{Slice{data}};
+    auto first = Decode<std::optional<uint8_t>>(reader);
+    auto second = Decode<std::optional<uint8_t>>(reader);
+    auto third = Decode<std::optional<uint8_t>>(reader);
+    ASSERT_EQ(first, Success);
+    ASSERT_EQ(second, Success);
+    EXPECT_NE(third, Success);
+    EXPECT_EQ(first.Get(), std::nullopt);
+    EXPECT_EQ(second.Get(), std::optional<uint8_t>{0x42});
+}
+
+TEST(BytesDecoderTest, Bitset) {
+    auto data = Data(0x2D, 0x6D);
+    auto reader = BufferReader{Slice{data}};
+    auto got = Decode<std::bitset<14>>(reader);
+    EXPECT_EQ(got, std::bitset<14>(0x6D2D));
 }
 
 TEST(BytesDecoderTest, Tuple) {
@@ -142,8 +182,8 @@
                      0x40, 0x50, 0x60, 0x70,  //
                      0x80);
     auto reader = BufferReader{Slice{data}};
-    EXPECT_THAT(Decode<T>(reader).Get(), (T{0x10u, 0x3020u, 0x70605040u}));
-    EXPECT_FALSE(Decode<T>(reader));
+    EXPECT_EQ(Decode<T>(reader), (T{0x10u, 0x3020u, 0x70605040u}));
+    EXPECT_NE(Decode<T>(reader), Success);
 }
 
 }  // namespace
diff --git a/src/tint/utils/bytes/reader_test.cc b/src/tint/utils/bytes/reader_test.cc
index 6b76e22..04ad6af 100644
--- a/src/tint/utils/bytes/reader_test.cc
+++ b/src/tint/utils/bytes/reader_test.cc
@@ -48,9 +48,9 @@
 TEST(BufferReaderTest, IntegerBigEndian_TooShort) {
     auto data = Data(0x10, 0x20);
     auto u32 = BufferReader{Slice{data}}.Int<uint32_t>(Endianness::kBig);
-    EXPECT_FALSE(u32);
+    EXPECT_NE(u32, Success);
     auto i32 = BufferReader{Slice{data}}.Int<int32_t>(Endianness::kBig);
-    EXPECT_FALSE(i32);
+    EXPECT_NE(i32, Success);
 }
 
 TEST(BufferReaderTest, IntegerLittleEndian) {
@@ -64,22 +64,22 @@
 TEST(BufferReaderTest, IntegerLittleEndian_TooShort) {
     auto data = Data(0x30, 0x40);
     auto u32 = BufferReader{Slice{data}}.Int<uint32_t>(Endianness::kLittle);
-    EXPECT_FALSE(u32);
+    EXPECT_NE(u32, Success);
     auto i32 = BufferReader{Slice{data}}.Int<int32_t>(Endianness::kLittle);
-    EXPECT_FALSE(i32);
+    EXPECT_NE(i32, Success);
 }
 
 TEST(BufferReaderTest, Float) {
     auto data = Data(0x00, 0x00, 0x08, 0x41);
     auto f32 = BufferReader{Slice{data}}.Float<float>();
-    ASSERT_TRUE(f32);
+    ASSERT_EQ(f32, Success);
     EXPECT_EQ(f32.Get(), 8.5f);
 }
 
 TEST(BufferReaderTest, Float_TooShort) {
     auto data = Data(0x08, 0x41);
     auto f32 = BufferReader{Slice{data}}.Float<float>();
-    EXPECT_FALSE(f32);
+    EXPECT_NE(f32, Success);
 }
 
 }  // namespace
diff --git a/src/tint/utils/cli/cli.h b/src/tint/utils/cli/cli.h
index b0e89e4..393024f 100644
--- a/src/tint/utils/cli/cli.h
+++ b/src/tint/utils/cli/cli.h
@@ -283,7 +283,7 @@
 
         if constexpr (is_number) {
             auto result = strconv::ParseNumber<T>(arg);
-            if (result) {
+            if (result == tint::Success) {
                 value = result.Get();
                 arguments.pop_front();
                 return Success;
diff --git a/src/tint/utils/cli/cli_test.cc b/src/tint/utils/cli/cli_test.cc
index 4db57f8..273d972 100644
--- a/src/tint/utils/cli/cli_test.cc
+++ b/src/tint/utils/cli/cli_test.cc
@@ -164,7 +164,7 @@
     opts.Add<BoolOption>("my_option", "a boolean value");
 
     auto res = opts.Parse(Split("--myoption false", " "));
-    ASSERT_FALSE(res) << res;
+    ASSERT_NE(res, Success);
     EXPECT_EQ(res.Failure().reason.str(), R"(error: unknown flag: --myoption
 Did you mean '--my_option'?)");
 }
@@ -177,7 +177,7 @@
     parse_opts.ignore_unknown = true;
 
     auto res = opts.Parse(Split("--myoption false", " "), parse_opts);
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_EQ(opt.value, std::nullopt);
 }
 
@@ -186,7 +186,7 @@
     auto& opt = opts.Add<BoolOption>("my_option", "a boolean value");
 
     auto res = opts.Parse(Split("--my_option unconsumed", " "));
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre("unconsumed"));
     EXPECT_EQ(opt.value, true);
 }
@@ -196,7 +196,7 @@
     auto& opt = opts.Add<BoolOption>("my_option", "a boolean value");
 
     auto res = opts.Parse(Split("--my_option true", " "));
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
     EXPECT_EQ(opt.value, true);
 }
@@ -206,7 +206,7 @@
     auto& opt = opts.Add<BoolOption>("my_option", "a boolean value", Default{true});
 
     auto res = opts.Parse(Split("--my_option false", " "));
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
     EXPECT_EQ(opt.value, false);
 }
@@ -216,7 +216,7 @@
     auto& opt = opts.Add<ValueOption<int>>("my_option", "an integer value");
 
     auto res = opts.Parse(Split("--my_option 42", " "));
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
     EXPECT_EQ(opt.value, 42);
 }
@@ -226,7 +226,7 @@
     auto& opt = opts.Add<ValueOption<uint64_t>>("my_option", "a uint64_t value");
 
     auto res = opts.Parse(Split("--my_option 1000000", " "));
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
     EXPECT_EQ(opt.value, 1000000);
 }
@@ -236,7 +236,7 @@
     auto& opt = opts.Add<ValueOption<float>>("my_option", "a float value");
 
     auto res = opts.Parse(Split("--my_option 1.25", " "));
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
     EXPECT_EQ(opt.value, 1.25f);
 }
@@ -246,7 +246,7 @@
     auto& opt = opts.Add<StringOption>("my_option", "a string value");
 
     auto res = opts.Parse(Split("--my_option blah", " "));
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
     EXPECT_EQ(opt.value, "blah");
 }
@@ -262,7 +262,7 @@
                                             EnumName(E::Z, "Z"),
                                         });
     auto res = opts.Parse(Split("--my_option Y", " "));
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
     EXPECT_EQ(opt.value, E::Y);
 }
@@ -272,7 +272,7 @@
     auto& opt = opts.Add<ValueOption<int>>("my_option", "an integer value", ShortName{"o"});
 
     auto res = opts.Parse(Split("-o 42", " "));
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
     EXPECT_EQ(opt.value, 42);
 }
@@ -282,7 +282,7 @@
     auto& opt = opts.Add<ValueOption<int32_t>>("my_option", "a int32_t value");
 
     auto res = opts.Parse(Split("abc --my_option -123 def", " "));
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre("abc", "def"));
     EXPECT_EQ(opt.value, -123);
 }
@@ -292,7 +292,7 @@
     auto& opt = opts.Add<ValueOption<int>>("my_option", "an int value");
 
     auto res = opts.Parse(Split("--my_option=123", " "));
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_THAT(ToStringList(res.Get()), testing::ElementsAre());
     EXPECT_EQ(opt.value, 123);
 }
@@ -302,7 +302,7 @@
     auto& opt = opts.Add<BoolOption>("my_option", "a boolean value", Default{true});
 
     auto res = opts.Parse(tint::Empty);
-    ASSERT_TRUE(res) << res;
+    ASSERT_EQ(res, Success);
     EXPECT_EQ(opt.value, true);
 }
 
diff --git a/src/tint/utils/reflection/reflection.h b/src/tint/utils/reflection/reflection.h
index bfb2ffa..d674db1 100644
--- a/src/tint/utils/reflection/reflection.h
+++ b/src/tint/utils/reflection/reflection.h
@@ -76,6 +76,20 @@
         }                                                          \
     }
 
+/// A template that can be specialized to reflect the valid range of an enum
+/// Use TINT_REFLECT_ENUM_RANGE to specialize this class
+template <typename T>
+struct EnumRange;
+
+/// Declares a specialization of EnumRange for the enum ENUM with the lowest enum value MIN and
+/// largest enum value MAX. Must only be used in the `tint` namespace.
+#define TINT_REFLECT_ENUM_RANGE(ENUM, MIN, MAX) \
+    template <>                                 \
+    struct EnumRange<ENUM> {                    \
+        static constexpr ENUM kMin = ENUM::MIN; \
+        static constexpr ENUM kMax = ENUM::MAX; \
+    }
+
 }  // namespace tint
 
 #endif  // SRC_TINT_UTILS_REFLECTION_REFLECTION_H_
diff --git a/src/tint/utils/result/result.h b/src/tint/utils/result/result.h
index d040294..a9e7a44 100644
--- a/src/tint/utils/result/result.h
+++ b/src/tint/utils/result/result.h
@@ -111,25 +111,13 @@
               typename = std::void_t<decltype(SUCCESS_TYPE{std::declval<S>()}),
                                      decltype(FAILURE_TYPE{std::declval<F>()})>>
     Result(const Result<S, F>& other) {  // NOLINT(runtime/explicit):
-        if (other) {
+        if (other == Success) {
             value = SUCCESS_TYPE{other.Get()};
         } else {
             value = FAILURE_TYPE{other.Failure()};
         }
     }
 
-    /// @returns true if the result was a success
-    operator bool() const {
-        Validate();
-        return std::holds_alternative<SUCCESS_TYPE>(value);
-    }
-
-    /// @returns true if the result was a failure
-    bool operator!() const {
-        Validate();
-        return std::holds_alternative<FAILURE_TYPE>(value);
-    }
-
     /// @returns the success value
     /// @warning attempting to call this when the Result holds an failure will result in UB.
     const SUCCESS_TYPE* operator->() const {
@@ -173,25 +161,54 @@
     }
 
     /// Equality operator
-    /// @param val the value to compare this Result to
-    /// @returns true if this result holds a success value equal to `value`
-    bool operator==(SUCCESS_TYPE val) const {
-        Validate();
-        if (auto* v = std::get_if<SUCCESS_TYPE>(&value)) {
-            return *v == val;
-        }
-        return false;
+    /// @param other the Result to compare this Result to
+    /// @returns true if this Result is equal to @p other
+    template <typename T>
+    bool operator==(const Result& other) const {
+        return value == other.value;
     }
 
     /// Equality operator
     /// @param val the value to compare this Result to
-    /// @returns true if this result holds a failure value equal to `value`
-    bool operator==(FAILURE_TYPE val) const {
+    /// @returns true if this result holds a success or failure value equal to `value`
+    template <typename T>
+    bool operator==(const T& val) const {
         Validate();
-        if (auto* v = std::get_if<FAILURE_TYPE>(&value)) {
-            return *v == val;
+
+        using D = std::decay_t<T>;
+        static constexpr bool is_success = std::is_same_v<D, tint::SuccessType>;  // T == Success
+        static constexpr bool is_success_ty =
+            std::is_same_v<D, SUCCESS_TYPE> ||
+            (traits::IsStringLike<SUCCESS_TYPE> && traits::IsStringLike<D>);  // T == SUCCESS_TYPE
+        static constexpr bool is_failure_ty =
+            std::is_same_v<D, FAILURE_TYPE> ||
+            (traits::IsStringLike<FAILURE_TYPE> && traits::IsStringLike<D>);  // T == FAILURE_TYPE
+
+        static_assert(is_success || is_success_ty || is_failure_ty,
+                      "unsupported type for Result equality operator");
+        static_assert(!(is_success_ty && is_failure_ty),
+                      "ambiguous success / failure type for Result equality operator");
+
+        if constexpr (is_success) {
+            return std::holds_alternative<SUCCESS_TYPE>(value);
+        } else if constexpr (is_success_ty) {
+            if (auto* v = std::get_if<SUCCESS_TYPE>(&value)) {
+                return *v == val;
+            }
+            return false;
+        } else if constexpr (is_failure_ty) {
+            if (auto* v = std::get_if<FAILURE_TYPE>(&value)) {
+                return *v == val;
+            }
+            return false;
         }
-        return false;
+    }
+    /// Inequality operator
+    /// @param val the value to compare this Result to
+    /// @returns false if this result holds a success or failure value equal to `value`
+    template <typename T>
+    bool operator!=(const T& val) const {
+        return !(*this == val);
     }
 
   private:
@@ -210,7 +227,7 @@
           typename FAILURE,
           typename = traits::EnableIfIsOStream<STREAM>>
 auto& operator<<(STREAM& out, const Result<SUCCESS, FAILURE>& res) {
-    if (res) {
+    if (res == Success) {
         if constexpr (traits::HasOperatorShiftLeft<STREAM&, SUCCESS>) {
             return out << "success: " << res.Get();
         } else {
diff --git a/src/tint/utils/result/result_test.cc b/src/tint/utils/result/result_test.cc
index 13cc05b..59edc47 100644
--- a/src/tint/utils/result/result_test.cc
+++ b/src/tint/utils/result/result_test.cc
@@ -34,33 +34,34 @@
 namespace tint {
 namespace {
 
+struct S {
+    int value;
+};
+static inline bool operator==(const S& a, const S& b) {
+    return a.value == b.value;
+}
+
 TEST(ResultTest, SuccessInt) {
     auto r = Result<int>(123);
-    EXPECT_TRUE(r);
-    EXPECT_FALSE(!r);
+    ASSERT_EQ(r, Success);
     EXPECT_EQ(r.Get(), 123);
 }
 
 TEST(ResultTest, SuccessStruct) {
-    struct S {
-        int value;
-    };
     auto r = Result<S>({123});
-    EXPECT_TRUE(r);
-    EXPECT_FALSE(!r);
+    ASSERT_EQ(r, Success);
     EXPECT_EQ(r->value, 123);
+    EXPECT_EQ(r, S{123});
 }
 
 TEST(ResultTest, Failure) {
     auto r = Result<int>(Failure{});
-    EXPECT_FALSE(r);
-    EXPECT_TRUE(!r);
+    EXPECT_NE(r, Success);
 }
 
 TEST(ResultTest, CustomFailure) {
     auto r = Result<int, std::string>("oh noes!");
-    EXPECT_FALSE(r);
-    EXPECT_TRUE(!r);
+    EXPECT_NE(r, Success);
     EXPECT_EQ(r.Failure(), "oh noes!");
 }