tint: Add generating IR protobufs to CLI

Adds the Tint IR protobuf binary format to the list of formats that
the CLI can output. Additionally adds a debugging flag for emitting a
human readable version of the protobuf.

Issue: 340582174

Change-Id: Ifea9d4699fa1c22b493f6bf67072081a9dda6b87
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/188320
Auto-Submit: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
Commit-Queue: Ryan Harrison <rharrison@chromium.org>
Commit-Queue: Ben Clayton <bclayton@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6abb9f0..7dc8b01 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -234,6 +234,9 @@
 
 option_if_not_defined(TINT_BUILD_SYNTAX_TREE_WRITER "Build the syntax tree writer" OFF)
 
+option_if_not_defined(TINT_BUILD_IR_BINARY "Build IR binary format support" ON)
+
+
 option_if_not_defined(TINT_BUILD_FUZZERS "Build fuzzers" OFF)
 option_if_not_defined(TINT_BUILD_AST_FUZZER "Build AST fuzzer" OFF)
 option_if_not_defined(TINT_BUILD_REGEX_FUZZER "Build regex fuzzer" OFF)
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index f17d0f3..af3d7a5 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -108,6 +108,12 @@
     defines += [ "TINT_BUILD_SYNTAX_TREE_WRITER=0" ]
   }
 
+  if (tint_build_ir_binary) {
+    defines += [ "TINT_BUILD_IR_BINARY=1" ]
+  } else {
+    defines += [ "TINT_BUILD_IR_BINARY=0" ]
+  }
+
   if (tint_build_is_win) {
     defines += [ "TINT_BUILD_IS_WIN=1" ]
   } else {
diff --git a/src/tint/cmd/tint/BUILD.bazel b/src/tint/cmd/tint/BUILD.bazel
index bdba7b0..05c18b3 100644
--- a/src/tint/cmd/tint/BUILD.bazel
+++ b/src/tint/cmd/tint/BUILD.bazel
@@ -97,6 +97,11 @@
     ],
     "//conditions:default": [],
   }) + select({
+    ":tint_build_ir_binary": [
+      "//src/tint/lang/core/ir/binary",
+    ],
+    "//conditions:default": [],
+  }) + select({
     ":tint_build_msl_writer": [
       "//src/tint/lang/msl/validate",
       "//src/tint/lang/msl/writer",
@@ -153,6 +158,11 @@
 )
 
 alias(
+  name = "tint_build_ir_binary",
+  actual = "//src/tint:tint_build_ir_binary_true",
+)
+
+alias(
   name = "tint_build_msl_writer",
   actual = "//src/tint:tint_build_msl_writer_true",
 )
diff --git a/src/tint/cmd/tint/BUILD.cmake b/src/tint/cmd/tint/BUILD.cmake
index 8ed82d1..5622503 100644
--- a/src/tint/cmd/tint/BUILD.cmake
+++ b/src/tint/cmd/tint/BUILD.cmake
@@ -102,6 +102,12 @@
   )
 endif(TINT_BUILD_HLSL_WRITER)
 
+if(TINT_BUILD_IR_BINARY)
+  tint_target_add_dependencies(tint_cmd_tint_cmd cmd
+    tint_lang_core_ir_binary
+  )
+endif(TINT_BUILD_IR_BINARY)
+
 if(TINT_BUILD_MSL_WRITER)
   tint_target_add_dependencies(tint_cmd_tint_cmd cmd
     tint_lang_msl_validate
diff --git a/src/tint/cmd/tint/BUILD.gn b/src/tint/cmd/tint/BUILD.gn
index 7f3584f..5edef93 100644
--- a/src/tint/cmd/tint/BUILD.gn
+++ b/src/tint/cmd/tint/BUILD.gn
@@ -99,6 +99,10 @@
     ]
   }
 
+  if (tint_build_ir_binary) {
+    deps += [ "${tint_src_dir}/lang/core/ir/binary" ]
+  }
+
   if (tint_build_msl_writer) {
     deps += [
       "${tint_src_dir}/lang/msl/validate",
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index f413485..ccfdcf1 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -68,6 +68,11 @@
 #if TINT_BUILD_WGSL_READER
 #include "src/tint/lang/wgsl/reader/program_to_ir/program_to_ir.h"
 #include "src/tint/lang/wgsl/reader/reader.h"
+
+#if TINT_BUILD_IR_BINARY
+#include "src/tint/lang/core/ir/binary/encode.h"
+#endif  // TINT_BUILD_IR_BINARY
+
 #endif  // TINT_BUILD_WGSL_READER
 
 #if TINT_BUILD_SPV_WRITER
@@ -105,6 +110,8 @@
 #define WGSL_READER_ONLY(x)
 #endif
 
+#define WGSL_READER_AND_IR_BINARY (TINT_BUILD_WGSL_READER && TINT_BUILD_IR_BINARY)
+
 #if TINT_BUILD_SPV_WRITER
 #define SPV_WRITER_ONLY(x) x
 #else
@@ -135,6 +142,12 @@
 #define GLSL_WRITER_ONLY(x)
 #endif
 
+#if WGSL_READER_AND_IR_BINARY
+#define WGSL_READER_AND_IR_BINARY_ONLY(x) x
+#else
+#define WGSL_READER_AND_IR_BINARY_ONLY(x)
+#endif
+
 namespace {
 
 /// Prints the given hash value in a format string that the end-to-end test runner can parse.
@@ -152,6 +165,7 @@
     kHlsl,
     kGlsl,
     kIr,
+    kIrBin
 };
 
 #if TINT_BUILD_HLSL_WRITER
@@ -216,6 +230,7 @@
 #endif  // TINT_BUILD_HLSL_WRITER
 
     bool dump_ir = false;
+    bool dump_ir_bin = false;
     bool use_ir = false;
     bool use_ir_reader = false;
 
@@ -285,6 +300,12 @@
     }
 #endif  // TINT_BUILD_HLSL_WRITER
 
+#if WGSL_READER_AND_IR_BINARY
+    if (tint::HasSuffix(filename, ".tirb")) {
+        return Format::kIrBin;
+    }
+#endif  // WGSL_READER_AND_IR_BINARY
+
     return Format::kUnknown;
 }
 
@@ -304,6 +325,7 @@
     HLSL_WRITER_ONLY(format_enum_names.Emplace(Format::kHlsl, "hlsl"));
     GLSL_WRITER_ONLY(format_enum_names.Emplace(Format::kGlsl, "glsl"));
     WGSL_READER_ONLY(format_enum_names.Emplace(Format::kIr, "ir"));
+    WGSL_READER_AND_IR_BINARY_ONLY(format_enum_names.Emplace(Format::kIrBin, "ir_bin"));
 
     OptionSet options;
     auto& fmt = options.Add<EnumOption<Format>>("format",
@@ -313,7 +335,8 @@
   .spv    -> spirv
   .wgsl   -> wgsl
   .metal  -> msl
-  .hlsl   -> hlsl)",
+  .hlsl   -> hlsl)" WGSL_READER_AND_IR_BINARY_ONLY(R"(
+  .tirb  -> ir binary protobuf)"),
                                                 format_enum_names, ShortName{"f"});
     TINT_DEFER(opts->format = fmt.value.value_or(Format::kUnknown));
 
@@ -370,6 +393,11 @@
                                             Default{false});
     TINT_DEFER(opts->dump_ir = *dump_ir.value);
 
+    auto& dump_ir_bin = options.Add<BoolOption>(
+        "dump-ir-bin", "Writes the IR as a human readable protobuf to stdout", Alias{"emit-ir-bin"},
+        Default{false});
+    TINT_DEFER(opts->dump_ir_bin = *dump_ir_bin.value);
+
     auto& use_ir = options.Add<BoolOption>(
         "use-ir", "Use the IR for writers and transforms when possible", Default{false});
     TINT_DEFER(opts->use_ir = *use_ir.value);
@@ -1223,12 +1251,76 @@
         std::cerr << "Failed to build IR from program: " << result.Failure() << "\n";
         return false;
     }
+
     options.printer->Print(tint::core::ir::Disassemble(result.Get()).Text());
     options.printer->Print(tint::StyledText{} << "\n");
+
     return true;
 #endif
 }
 
+/// Generate IR binary protobuf for a program.
+/// @param program the program to generate
+/// @param options the options that Tint was invoked with
+/// @returns true on success
+bool GenerateIrProtoBinary([[maybe_unused]] const tint::Program& program,
+                           [[maybe_unused]] const Options& options) {
+#if !TINT_BUILD_WGSL_READER
+    std::cerr << "WGSL reader not enabled in tint build" << std::endl;
+    return false;
+#elif !TINT_BUILD_IR_BINARY
+    std::cerr << "IR binary not enabled in tint build" << std::endl;
+    return false;
+#else
+    auto module = tint::wgsl::reader::ProgramToLoweredIR(program);
+    if (module != tint::Success) {
+        std::cerr << "Failed to build IR from program: " << module.Failure() << "\n";
+        return false;
+    }
+    auto pb = tint::core::ir::binary::Encode(module.Get());
+    if (pb != tint::Success) {
+        std::cerr << "Failed to encode IR module to protobuf: " << pb.Failure() << "\n";
+        return false;
+    }
+
+    if (!WriteFile(options.output_file, "wb", ToStdVector(pb.Get()))) {
+        std::cerr << "Failed to write protobuf binary out to file"
+                  << "\n";
+        return false;
+    }
+
+    return true;
+#endif
+}
+
+/// Generate IR human readable protobuf for a program.
+/// @param program the program to generate
+/// @param options the options that Tint was invoked with
+/// @returns true on success
+#if WGSL_READER_AND_IR_BINARY
+bool GenerateIrProtoDebug([[maybe_unused]] const tint::Program& program,
+                          [[maybe_unused]] const Options& options) {
+    auto module = tint::wgsl::reader::ProgramToLoweredIR(program);
+    if (module != tint::Success) {
+        std::cerr << "Failed to build IR from program: " << module.Failure() << "\n";
+        return false;
+    }
+    auto pb = tint::core::ir::binary::EncodeDebug(module.Get());
+    if (pb != tint::Success) {
+        std::cerr << "Failed to encode IR module to protobuf: " << pb.Failure() << "\n";
+        return false;
+    }
+
+    if (!WriteFile(options.output_file, "w", pb.Get())) {
+        std::cerr << "Failed to write protobuf debug text out to file"
+                  << "\n";
+        return false;
+    }
+
+    return true;
+}
+#endif  // WGSL_READER_AND_IR_BINARY
+
 }  // namespace
 
 int main(int argc, const char** argv) {
@@ -1370,6 +1462,12 @@
     }
 #endif  // TINT_BUILD_WGSL_READER
 
+#if WGSL_READER_AND_IR_BINARY
+    if (options.dump_ir_bin) {
+        GenerateIrProtoDebug(info.program, options);
+    }
+#endif  // WGSL_READER_AND_IR_BINARY
+
     tint::inspector::Inspector inspector(info.program);
     if (options.dump_inspector_bindings) {
         tint::cmd::PrintInspectorBindings(inspector);
@@ -1480,6 +1578,9 @@
         case Format::kIr:
             success = GenerateIr(program, options);
             break;
+        case Format::kIrBin:
+            success = GenerateIrProtoBinary(program, options);
+            break;
         case Format::kNone:
             break;
         default:
diff --git a/src/tint/lang/core/ir/binary/encode.cc b/src/tint/lang/core/ir/binary/encode.cc
index 74d00ac..d908788 100644
--- a/src/tint/lang/core/ir/binary/encode.cc
+++ b/src/tint/lang/core/ir/binary/encode.cc
@@ -27,6 +27,7 @@
 
 #include "src/tint/lang/core/ir/binary/encode.h"
 
+#include <string>
 #include <utility>
 
 #include "src/tint/lang/core/builtin_fn.h"
@@ -1153,4 +1154,13 @@
     return buffer;
 }
 
+Result<std::string> EncodeDebug(const Module& mod_in) {
+    GOOGLE_PROTOBUF_VERIFY_VERSION;
+
+    pb::Module mod_out;
+    Encoder{mod_in, mod_out}.Encode();
+
+    return mod_out.DebugString();
+}
+
 }  // namespace tint::core::ir::binary
diff --git a/src/tint/lang/core/ir/binary/encode.h b/src/tint/lang/core/ir/binary/encode.h
index 3cdd520..5095eff 100644
--- a/src/tint/lang/core/ir/binary/encode.h
+++ b/src/tint/lang/core/ir/binary/encode.h
@@ -28,18 +28,24 @@
 #ifndef SRC_TINT_LANG_CORE_IR_BINARY_ENCODE_H_
 #define SRC_TINT_LANG_CORE_IR_BINARY_ENCODE_H_
 
+#include <string>
+
 #include "src/tint/utils/containers/vector.h"
 #include "src/tint/utils/result/result.h"
 
-// Forward declarartion
+// Forward declaration
 namespace tint::core::ir {
 class Module;
 }  // namespace tint::core::ir
 
 namespace tint::core::ir::binary {
 
+// Encode the module into a binary representation.
 Result<Vector<std::byte, 0>> Encode(const Module& module);
 
+// Encode the module into a human readable debug representation.
+Result<std::string> EncodeDebug(const Module& module);
+
 }  // namespace tint::core::ir::binary
 
 #endif  // SRC_TINT_LANG_CORE_IR_BINARY_ENCODE_H_