[tint][fuzz][ir] Implement test case disassembler

Adds in ir_fuzz_dis, which takes in an IR test case binary proto and
emits the corresponding WGSL.

Also fixes up the usage message for ir_fuzz_as saying it was tint.

Fixes: 345204573

Change-Id: I2d4e77d0e24066d257d87cde3fbcc24aa5c4692e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/196115
Reviewed-by: dan sinclair <dsinclair@chromium.org>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: James Price <jrprice@google.com>
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: James Price <jrprice@google.com>
diff --git a/src/tint/cmd/fuzz/ir/BUILD.cmake b/src/tint/cmd/fuzz/ir/BUILD.cmake
index c56f524..489d2a6 100644
--- a/src/tint/cmd/fuzz/ir/BUILD.cmake
+++ b/src/tint/cmd/fuzz/ir/BUILD.cmake
@@ -35,6 +35,7 @@
 ################################################################################
 
 include(cmd/fuzz/ir/as/BUILD.cmake)
+include(cmd/fuzz/ir/dis/BUILD.cmake)
 
 if(TINT_BUILD_IR_BINARY AND TINT_BUILD_IR_FUZZER AND ((NOT IS_ASAN) OR BUILD_WITH_CHROMIUM))
 ################################################################################
diff --git a/src/tint/cmd/fuzz/ir/as/main.cc b/src/tint/cmd/fuzz/ir/as/main.cc
index aa381a8..64b6c49 100644
--- a/src/tint/cmd/fuzz/ir/as/main.cc
+++ b/src/tint/cmd/fuzz/ir/as/main.cc
@@ -103,7 +103,7 @@
 
     auto show_usage = [&] {
         std::cout
-            << R"(Usage: tint [options] [-o|--output-filename] <output-file> <input-file> or tint [options] <io-dir>
+            << R"(Usage: ir_fuzz_as [options] [-o|--output-filename] <output-file> <input-file> or tint [options] <io-dir>
 If a single WGSL file is provided, the suffix of the input file is not checked, and
 '-o|--output-filename' must be provided.
 
diff --git a/src/tint/cmd/fuzz/ir/dis/BUILD.bazel b/src/tint/cmd/fuzz/ir/dis/BUILD.bazel
new file mode 100644
index 0000000..b427ad4
--- /dev/null
+++ b/src/tint/cmd/fuzz/ir/dis/BUILD.bazel
@@ -0,0 +1,135 @@
+# 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.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.bazel.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+load("//src/tint:flags.bzl", "COPTS")
+load("@bazel_skylib//lib:selects.bzl", "selects")
+cc_binary(
+  name = "cmd",
+  srcs = [
+    "main.cc",
+  ],
+  deps = [
+    "//src/tint/api",
+    "//src/tint/api/common",
+    "//src/tint/cmd/common",
+    "//src/tint/lang/core",
+    "//src/tint/lang/core/constant",
+    "//src/tint/lang/core/ir",
+    "//src/tint/lang/core/type",
+    "//src/tint/lang/wgsl",
+    "//src/tint/lang/wgsl/ast",
+    "//src/tint/lang/wgsl/common",
+    "//src/tint/lang/wgsl/features",
+    "//src/tint/lang/wgsl/inspector",
+    "//src/tint/lang/wgsl/program",
+    "//src/tint/lang/wgsl/sem",
+    "//src/tint/lang/wgsl/writer/ir_to_program",
+    "//src/tint/utils/cli",
+    "//src/tint/utils/containers",
+    "//src/tint/utils/diagnostic",
+    "//src/tint/utils/ice",
+    "//src/tint/utils/id",
+    "//src/tint/utils/macros",
+    "//src/tint/utils/math",
+    "//src/tint/utils/memory",
+    "//src/tint/utils/reflection",
+    "//src/tint/utils/result",
+    "//src/tint/utils/rtti",
+    "//src/tint/utils/strconv",
+    "//src/tint/utils/symbol",
+    "//src/tint/utils/text",
+    "//src/tint/utils/traits",
+  ] + select({
+    ":tint_build_ir_binary": [
+      "//src/tint/lang/core/ir/binary",
+    ],
+    "//conditions:default": [],
+  }) + select({
+    ":tint_build_ir_binary_and_tint_build_ir_fuzzer": [
+      "",
+    ],
+    "//conditions:default": [],
+  }) + select({
+    ":tint_build_spv_reader": [
+      "//src/tint/lang/spirv/reader/common",
+    ],
+    "//conditions:default": [],
+  }) + select({
+    ":tint_build_wgsl_writer": [
+      "//src/tint/lang/wgsl/writer",
+    ],
+    "//conditions:default": [],
+  }),
+  copts = COPTS,
+  visibility = ["//visibility:public"],
+)
+
+alias(
+  name = "tint_build_ir_binary",
+  actual = "//src/tint:tint_build_ir_binary_true",
+)
+
+alias(
+  name = "tint_build_ir_fuzzer",
+  actual = "//src/tint:tint_build_ir_fuzzer_true",
+)
+
+alias(
+  name = "tint_build_spv_reader",
+  actual = "//src/tint:tint_build_spv_reader_true",
+)
+
+alias(
+  name = "tint_build_wgsl_writer",
+  actual = "//src/tint:tint_build_wgsl_writer_true",
+)
+
+selects.config_setting_group(
+    name = "tint_build_ir_binary_and_tint_build_ir_fuzzer",
+    match_all = [
+        ":tint_build_ir_binary",
+        ":tint_build_ir_fuzzer",
+    ],
+)
+selects.config_setting_group(
+    name = "tint_build_ir_binary_and_tint_build_wgsl_writer_and_tint_build_ir_fuzzer",
+    match_all = [
+        ":tint_build_ir_binary",
+        ":tint_build_wgsl_writer",
+        ":tint_build_ir_fuzzer",
+    ],
+)
+
diff --git a/src/tint/cmd/fuzz/ir/dis/BUILD.cfg b/src/tint/cmd/fuzz/ir/dis/BUILD.cfg
new file mode 100644
index 0000000..8038547
--- /dev/null
+++ b/src/tint/cmd/fuzz/ir/dis/BUILD.cfg
@@ -0,0 +1,7 @@
+{
+    "cmd": {
+        "Condition": "tint_build_ir_binary && tint_build_wgsl_writer && tint_build_ir_fuzzer",
+        "OutputName": "ir_fuzz_dis",
+        "Internal": [ "utils/protos/ir_fuzz:proto" ],
+    },
+}
diff --git a/src/tint/cmd/fuzz/ir/dis/BUILD.cmake b/src/tint/cmd/fuzz/ir/dis/BUILD.cmake
new file mode 100644
index 0000000..e1ab456
--- /dev/null
+++ b/src/tint/cmd/fuzz/ir/dis/BUILD.cmake
@@ -0,0 +1,106 @@
+# 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.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.cmake.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+if(TINT_BUILD_IR_BINARY AND TINT_BUILD_WGSL_WRITER AND TINT_BUILD_IR_FUZZER)
+################################################################################
+# Target:    tint_cmd_fuzz_ir_dis_cmd
+# Kind:      cmd
+# Condition: TINT_BUILD_IR_BINARY AND TINT_BUILD_WGSL_WRITER AND TINT_BUILD_IR_FUZZER
+################################################################################
+tint_add_target(tint_cmd_fuzz_ir_dis_cmd cmd
+  cmd/fuzz/ir/dis/main.cc
+)
+
+tint_target_add_dependencies(tint_cmd_fuzz_ir_dis_cmd cmd
+  tint_api
+  tint_api_common
+  tint_cmd_common
+  tint_lang_core
+  tint_lang_core_constant
+  tint_lang_core_ir
+  tint_lang_core_type
+  tint_lang_wgsl
+  tint_lang_wgsl_ast
+  tint_lang_wgsl_common
+  tint_lang_wgsl_features
+  tint_lang_wgsl_inspector
+  tint_lang_wgsl_program
+  tint_lang_wgsl_sem
+  tint_lang_wgsl_writer_ir_to_program
+  tint_utils_cli
+  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_strconv
+  tint_utils_symbol
+  tint_utils_text
+  tint_utils_traits
+)
+
+if(TINT_BUILD_IR_BINARY)
+  tint_target_add_dependencies(tint_cmd_fuzz_ir_dis_cmd cmd
+    tint_lang_core_ir_binary
+  )
+endif(TINT_BUILD_IR_BINARY)
+
+if(TINT_BUILD_IR_BINARY AND TINT_BUILD_IR_FUZZER)
+  tint_target_add_dependencies(tint_cmd_fuzz_ir_dis_cmd cmd
+    tint_utils_protos_ir_fuzz_proto
+  )
+endif(TINT_BUILD_IR_BINARY AND TINT_BUILD_IR_FUZZER)
+
+if(TINT_BUILD_SPV_READER)
+  tint_target_add_dependencies(tint_cmd_fuzz_ir_dis_cmd cmd
+    tint_lang_spirv_reader_common
+  )
+endif(TINT_BUILD_SPV_READER)
+
+if(TINT_BUILD_WGSL_WRITER)
+  tint_target_add_dependencies(tint_cmd_fuzz_ir_dis_cmd cmd
+    tint_lang_wgsl_writer
+  )
+endif(TINT_BUILD_WGSL_WRITER)
+
+tint_target_set_output_name(tint_cmd_fuzz_ir_dis_cmd cmd "ir_fuzz_dis")
+
+endif(TINT_BUILD_IR_BINARY AND TINT_BUILD_WGSL_WRITER AND TINT_BUILD_IR_FUZZER)
\ No newline at end of file
diff --git a/src/tint/cmd/fuzz/ir/dis/BUILD.gn b/src/tint/cmd/fuzz/ir/dis/BUILD.gn
new file mode 100644
index 0000000..6b47007
--- /dev/null
+++ b/src/tint/cmd/fuzz/ir/dis/BUILD.gn
@@ -0,0 +1,93 @@
+# 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.
+
+################################################################################
+# File generated by 'tools/src/cmd/gen' using the template:
+#   tools/src/cmd/gen/build/BUILD.gn.tmpl
+#
+# To regenerate run: './tools/run gen'
+#
+#                       Do not modify this file directly
+################################################################################
+
+import("../../../../../../scripts/tint_overrides_with_defaults.gni")
+
+import("${tint_src_dir}/tint.gni")
+if (tint_build_ir_binary && tint_build_wgsl_writer && tint_build_ir_fuzzer) {
+  tint_executable("dis") {
+    output_name = "ir_fuzz_dis"
+    sources = [ "main.cc" ]
+    deps = [
+      "${tint_src_dir}/api",
+      "${tint_src_dir}/api/common",
+      "${tint_src_dir}/cmd/common",
+      "${tint_src_dir}/lang/core",
+      "${tint_src_dir}/lang/core/constant",
+      "${tint_src_dir}/lang/core/ir",
+      "${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/inspector",
+      "${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/cli",
+      "${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/strconv",
+      "${tint_src_dir}/utils/symbol",
+      "${tint_src_dir}/utils/text",
+      "${tint_src_dir}/utils/traits",
+    ]
+
+    if (tint_build_ir_binary) {
+      deps += [ "${tint_src_dir}/lang/core/ir/binary" ]
+    }
+
+    if (tint_build_ir_binary && tint_build_ir_fuzzer) {
+      deps += [ "${tint_src_dir}/utils/protos/ir_fuzz:proto" ]
+    }
+
+    if (tint_build_spv_reader) {
+      deps += [ "${tint_src_dir}/lang/spirv/reader/common" ]
+    }
+
+    if (tint_build_wgsl_writer) {
+      deps += [ "${tint_src_dir}/lang/wgsl/writer" ]
+    }
+  }
+}
diff --git a/src/tint/cmd/fuzz/ir/dis/main.cc b/src/tint/cmd/fuzz/ir/dis/main.cc
new file mode 100644
index 0000000..28e7692
--- /dev/null
+++ b/src/tint/cmd/fuzz/ir/dis/main.cc
@@ -0,0 +1,185 @@
+// Copyright 2024 The Dawn & Tint Authors
+//
+// Redistribution and use in source and binary forms, with or without
+// modification, are permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this
+//    list of conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice,
+//    this list of conditions and the following disclaimer in the documentation
+//    and/or other materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its
+//    contributors may be used to endorse or promote products derived from
+//    this software without specific prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include <fstream>
+#include <iostream>
+#include <memory>
+#include <string>
+
+#include "src/tint/api/tint.h"
+#include "src/tint/cmd/common/helper.h"
+#include "src/tint/lang/core/ir/binary/decode.h"
+#include "src/tint/lang/core/ir/disassembler.h"
+#include "src/tint/lang/wgsl/writer/writer.h"
+#include "src/tint/utils/cli/cli.h"
+#include "src/tint/utils/containers/transform.h"
+#include "src/tint/utils/macros/defer.h"
+#include "src/tint/utils/text/color_mode.h"
+#include "src/tint/utils/text/string.h"
+#include "src/tint/utils/text/styled_text.h"
+#include "src/tint/utils/text/styled_text_printer.h"
+
+TINT_BEGIN_DISABLE_PROTOBUF_WARNINGS();
+#include "src/tint/utils/protos/ir_fuzz/ir_fuzz.pb.h"
+TINT_END_DISABLE_PROTOBUF_WARNINGS();
+
+namespace {
+
+struct Options {
+    std::unique_ptr<tint::StyledTextPrinter> printer;
+
+    std::string input_filename;
+    std::string output_filename;
+
+    bool dump_ir = false;
+};
+
+bool ParseArgs(tint::VectorRef<std::string_view> arguments, Options* opts) {
+    using namespace tint::cli;  // NOLINT(build/namespaces)
+
+    OptionSet options;
+    auto& col = options.Add<EnumOption<tint::ColorMode>>(
+        "color", "Use colored output",
+        tint::Vector{
+            EnumName{tint::ColorMode::kPlain, "off"},
+            EnumName{tint::ColorMode::kDark, "dark"},
+            EnumName{tint::ColorMode::kLight, "light"},
+        },
+        ShortName{"col"}, Default{tint::ColorModeDefault()});
+    TINT_DEFER(opts->printer = CreatePrinter(*col.value));
+
+    auto& output = options.Add<StringOption>(
+        "output-filename", "Output file name, if not specified, WGSL output will go to STDOUT",
+        ShortName{"o"}, Parameter{"name"});
+    TINT_DEFER(opts->output_filename = output.value.value_or(""));
+
+    auto& dump_ir = options.Add<BoolOption>("dump-ir", "Writes the IR form of input to stdout",
+                                            Alias{"emit-ir"}, Default{false});
+    TINT_DEFER(opts->dump_ir = *dump_ir.value);
+
+    auto& help = options.Add<BoolOption>("help", "Show usage", ShortName{"h"});
+
+    auto show_usage = [&] {
+        std::cout << R"(Usage: ir_fuzz_dis [options] <input-file>
+
+Options:
+)";
+        options.ShowHelp(std::cout);
+    };
+
+    auto result = options.Parse(arguments);
+    if (result != tint::Success) {
+        std::cerr << result.Failure() << "\n";
+        show_usage();
+        return false;
+    }
+    if (help.value.value_or(false)) {
+        show_usage();
+        return false;
+    }
+
+    auto args = result.Get();
+    if (args.Length() > 1) {
+        std::cerr << "More than one input arg specified: "
+                  << tint::Join(Transform(args, tint::Quote), ", ") << "\n";
+        return false;
+    }
+    if (args.Length() == 1) {
+        opts->input_filename = args[0];
+    }
+
+    return true;
+}
+
+/// @returns a fuzzer test case protobuf for the given file
+/// @param options program options that contains the filename to be read, etc.
+tint::Result<tint::cmd::fuzz::ir::pb::Root> GenerateFuzzCaseProto(const Options& options) {
+    tint::cmd::fuzz::ir::pb::Root fuzz_pb;
+
+    std::fstream input(options.input_filename, std::ios::in | std::ios::binary);
+    if (!fuzz_pb.ParseFromIstream(&input)) {
+        return tint::Failure{"Unable to parse bytes into test case protobuf"};
+    }
+
+    return std::move(fuzz_pb);
+}
+
+bool ProcessFile(const Options& options) {
+    auto fuzz_pb = GenerateFuzzCaseProto(options);
+    if (fuzz_pb != tint::Success) {
+        std::cerr << "Failed to read test case protobuf: " << fuzz_pb.Failure() << "\n";
+        return false;
+    }
+
+    auto module = tint::core::ir::binary::Decode(fuzz_pb.Get().module());
+    if (module != tint::Success) {
+        std::cerr << "Unable to decode ir protobuf from test case protobuf: " << module.Failure()
+                  << "\n";
+        return false;
+    }
+
+    if (options.dump_ir) {
+        options.printer->Print(tint::core::ir::Disassembler(module.Get()).Text());
+        options.printer->Print(tint::StyledText{} << "\n");
+    }
+
+    tint::wgsl::writer::ProgramOptions writer_options;
+    auto output = tint::wgsl::writer::WgslFromIR(module.Get(), writer_options);
+    if (output != tint::Success) {
+        std::cerr << "Failed to convert IR to Program: " << output.Failure() << "\n";
+        return false;
+    }
+
+    return tint::cmd::WriteFile(options.output_filename, "w", output->wgsl);
+}
+
+}  // namespace
+
+int main(int argc, const char** argv) {
+    tint::Vector<std::string_view, 8> arguments;
+    for (int i = 1; i < argc; i++) {
+        std::string_view arg(argv[i]);
+        if (!arg.empty()) {
+            arguments.Push(argv[i]);
+        }
+    }
+
+    Options options;
+
+    tint::Initialize();
+    tint::SetInternalCompilerErrorReporter(&tint::cmd::TintInternalCompilerErrorReporter);
+
+    if (!ParseArgs(arguments, &options)) {
+        return EXIT_FAILURE;
+    }
+
+    if (!ProcessFile(options)) {
+        return EXIT_FAILURE;
+    }
+
+    return EXIT_SUCCESS;
+}