[tint] Refactor common pre-backend code

Converting AST to IR and then running SingleEntryPoint and
SubstituteOverrides was the same for every backend. Pull it out to the
`generate` helper instead.

Change-Id: I76a1018942af8c4150883e5186eb25962645fb7e
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/250818
Reviewed-by: dan sinclair <dsinclair@chromium.org>
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index 6a78520..a6dea71 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -46,6 +46,7 @@
 #include "src/tint/lang/core/ir/transform/single_entry_point.h"
 #include "src/tint/lang/core/ir/transform/substitute_overrides.h"
 #include "src/tint/lang/core/ir/var.h"
+#include "src/tint/lang/core/type/f16.h"
 #include "src/tint/lang/msl/ir/transform/flatten_bindings.h"
 #include "src/tint/lang/wgsl/ast/module.h"
 #include "src/tint/utils/command/cli.h"
@@ -747,43 +748,13 @@
 /// Generate SPIR-V code for a program.
 /// @param options the options that Tint was invoked with
 /// @param inspector the inspector
-/// @param src_program the program to generate
+/// @param ir the module to generate
 /// @returns true on success
+[[maybe_unused]]
 bool GenerateSpirv([[maybe_unused]] Options& options,
                    [[maybe_unused]] tint::inspector::Inspector& inspector,
-                   [[maybe_unused]] tint::Program& src_program) {
+                   [[maybe_unused]] tint::core::ir::Module& ir) {
 #if TINT_BUILD_SPV_WRITER
-    // Convert the AST program to an IR module.
-    auto ir = tint::wgsl::reader::ProgramToLoweredIR(src_program);
-    if (ir != tint::Success) {
-        std::cerr << "Failed to generate IR: " << ir << "\n";
-        return false;
-    }
-
-    if (options.ep_name != "") {
-        auto singleEntryPointResult =
-            tint::core::ir::transform::SingleEntryPoint(ir.Get(), options.ep_name);
-        if (singleEntryPointResult != tint::Success) {
-            std::cerr << "Pipeline single entry point (IR) failed:\n"
-                      << singleEntryPointResult.Failure() << "\n";
-        }
-    }
-
-    // this needs to run after SingleEntryPoint transform which removes unused
-    // overrides for the current entry point.
-    auto substitute_override_cfg = CreateOverrideMap(options, inspector);
-    if (substitute_override_cfg != tint::Success) {
-        std::cerr << "Failed to create override map: " << substitute_override_cfg.Failure() << "\n";
-        return false;
-    }
-    auto substituteOverridesResult =
-        tint::core::ir::transform::SubstituteOverrides(ir.Get(), substitute_override_cfg.Get());
-    if (substituteOverridesResult != tint::Success) {
-        std::cerr << "Pipeline override substitution (IR) failed:\n"
-                  << substituteOverridesResult.Failure() << "\n";
-        return false;
-    }
-
     tint::spirv::writer::Options gen_options;
     if (options.rename_all) {
         gen_options.remapped_entry_point_name = "tint_entry_point";
@@ -806,26 +777,26 @@
         offset += 8;
     }
 
-    gen_options.bindings = tint::spirv::writer::GenerateBindings(ir.Get());
+    gen_options.bindings = tint::spirv::writer::GenerateBindings(ir);
 
     // Enable the Vulkan Memory Model if needed.
-    for (auto* enable : src_program.AST().Enables()) {
-        if (enable->HasExtension(tint::wgsl::Extension::kChromiumExperimentalSubgroupMatrix)) {
+    for (auto* ty : ir.Types()) {
+        if (ty->Is<tint::core::type::SubgroupMatrix>()) {
             gen_options.use_vulkan_memory_model = true;
         }
     }
 
     // Check that the module and options are supported by the backend.
-    auto check = tint::spirv::writer::CanGenerate(ir.Get(), gen_options);
+    auto check = tint::spirv::writer::CanGenerate(ir, gen_options);
     if (check != tint::Success) {
         std::cerr << check.Failure() << "\n";
         return false;
     }
 
     // Generate SPIR-V from Tint IR.
-    auto result = tint::spirv::writer::Generate(ir.Get(), gen_options);
+    auto result = tint::spirv::writer::Generate(ir, gen_options);
     if (result != tint::Success) {
-        tint::cmd::PrintWGSL(std::cerr, src_program);
+        options.printer->Print(tint::core::ir::Disassembler(ir).Text());
         std::cerr << "Failed to generate SPIR-V: " << result.Failure() << "\n";
         return false;
     }
@@ -916,46 +887,16 @@
 /// Generate MSL code for a program.
 /// @param options the options that Tint was invoked with
 /// @param inspector the inspector
-/// @param src_program the program to generate
+/// @param ir the module to generate
 /// @returns true on success
+[[maybe_unused]]
 bool GenerateMsl([[maybe_unused]] Options& options,
                  [[maybe_unused]] tint::inspector::Inspector& inspector,
-                 [[maybe_unused]] tint::Program& src_program) {
+                 [[maybe_unused]] tint::core::ir::Module& ir) {
 #if TINT_BUILD_MSL_WRITER
-    // Convert the AST program to an IR module.
-    auto ir = tint::wgsl::reader::ProgramToLoweredIR(src_program);
-    if (ir != tint::Success) {
-        std::cerr << "Failed to generate IR: " << ir << "\n";
-        return false;
-    }
-
-    if (options.ep_name != "") {
-        auto singleEntryPointResult =
-            tint::core::ir::transform::SingleEntryPoint(ir.Get(), options.ep_name);
-        if (singleEntryPointResult != tint::Success) {
-            std::cerr << "Pipeline single entry point (IR) failed:\n"
-                      << singleEntryPointResult.Failure() << "\n";
-        }
-    }
-
-    // this needs to run after SingleEntryPoint transform which removes unused
-    // overrides for the current entry point.
-    auto substitute_override_cfg = CreateOverrideMap(options, inspector);
-    if (substitute_override_cfg != tint::Success) {
-        std::cerr << "Failed to create override map: " << substitute_override_cfg.Failure() << "\n";
-        return false;
-    }
-    auto substituteOverridesResult =
-        tint::core::ir::transform::SubstituteOverrides(ir.Get(), substitute_override_cfg.Get());
-    if (substituteOverridesResult != tint::Success) {
-        std::cerr << "Pipeline override substitution (IR) failed:\n"
-                  << substituteOverridesResult.Failure() << "\n";
-        return false;
-    }
-
     {
         // Remap resource numbers to a flat namespace.
-        auto res = tint::msl::ir::transform::FlattenBindings(ir.Get());
+        auto res = tint::msl::ir::transform::FlattenBindings(ir);
         if (res != tint::Success) {
             std::cerr << "Failed to flatten bindings: " << res.Failure().reason << "\n";
             return false;
@@ -971,14 +912,14 @@
     gen_options.disable_robustness = !options.enable_robustness;
     gen_options.disable_workgroup_init = options.disable_workgroup_init;
     gen_options.pixel_local_attachments = options.pixel_local_attachments;
-    gen_options.bindings = tint::msl::writer::GenerateBindings(ir.Get());
+    gen_options.bindings = tint::msl::writer::GenerateBindings(ir);
     gen_options.array_length_from_uniform.ubo_binding = 30;
     gen_options.disable_demote_to_helper = options.disable_demote_to_helper;
     gen_options.use_argument_buffers = options.use_argument_buffers;
 
     // Add array_length_from_uniform entries for all storage buffers with runtime sized arrays.
     std::unordered_set<tint::BindingPoint> storage_bindings;
-    for (auto* inst : *ir->root_block) {
+    for (auto* inst : *ir.root_block) {
         auto* var = inst->As<tint::core::ir::Var>();
         if (!var) {
             continue;
@@ -999,15 +940,15 @@
     }
 
     // Check that the module and options are supported by the backend.
-    auto check = tint::msl::writer::CanGenerate(ir.Get(), gen_options);
+    auto check = tint::msl::writer::CanGenerate(ir, gen_options);
     if (check != tint::Success) {
         std::cerr << check.Failure() << "\n";
         return false;
     }
 
-    auto result = tint::msl::writer::Generate(ir.Get(), gen_options);
+    auto result = tint::msl::writer::Generate(ir, gen_options);
     if (result != tint::Success) {
-        tint::cmd::PrintWGSL(std::cerr, src_program);
+        options.printer->Print(tint::core::ir::Disassembler(ir).Text());
         std::cerr << "Failed to generate: " << result.Failure() << "\n";
         return false;
     }
@@ -1059,11 +1000,12 @@
 /// Generate HLSL code for a program.
 /// @param options the options that Tint was invoked with
 /// @param inspector the inspector
-/// @param src_program the program to generate
+/// @param ir the module to generate
 /// @returns true on success
+[[maybe_unused]]
 bool GenerateHlsl([[maybe_unused]] Options& options,
                   [[maybe_unused]] tint::inspector::Inspector& inspector,
-                  [[maybe_unused]] tint::Program& src_program) {
+                  [[maybe_unused]] tint::core::ir::Module& ir) {
 #if TINT_BUILD_HLSL_WRITER
     const bool for_fxc = options.format == Format::kHlslFxc;
     // Set up the backend options.
@@ -1080,49 +1022,18 @@
         options.hlsl_shader_model < kMinShaderModelForPackUnpack4x8InHLSL;
     gen_options.compiler = for_fxc ? tint::hlsl::writer::Options::Compiler::kFXC
                                    : tint::hlsl::writer::Options::Compiler::kDXC;
-
-    // Convert the AST program to an IR module.
-    auto ir = tint::wgsl::reader::ProgramToLoweredIR(src_program);
-    if (ir != tint::Success) {
-        std::cerr << "Failed to generate IR: " << ir << "\n";
-        return false;
-    }
-    gen_options.bindings = tint::hlsl::writer::GenerateBindings(ir.Get());
-
-    if (options.ep_name != "") {
-        auto singleEntryPointResult =
-            tint::core::ir::transform::SingleEntryPoint(ir.Get(), options.ep_name);
-        if (singleEntryPointResult != tint::Success) {
-            std::cerr << "Pipeline single entry point (IR) failed:\n"
-                      << singleEntryPointResult.Failure() << "\n";
-        }
-    }
-
-    // this needs to run after SingleEntryPoint transform which removes unused
-    // overrides for the current entry point.
-    auto substitute_override_cfg = CreateOverrideMap(options, inspector);
-    if (substitute_override_cfg != tint::Success) {
-        std::cerr << "Failed to create override map: " << substitute_override_cfg.Failure() << "\n";
-        return false;
-    }
-    auto substituteOverridesResult =
-        tint::core::ir::transform::SubstituteOverrides(ir.Get(), substitute_override_cfg.Get());
-    if (substituteOverridesResult != tint::Success) {
-        std::cerr << "Pipeline override substitution (IR) failed:\n"
-                  << substituteOverridesResult.Failure() << "\n";
-        return false;
-    }
+    gen_options.bindings = tint::hlsl::writer::GenerateBindings(ir);
 
     // Check that the module and options are supported by the backend.
-    auto check = tint::hlsl::writer::CanGenerate(ir.Get(), gen_options);
+    auto check = tint::hlsl::writer::CanGenerate(ir, gen_options);
     if (check != tint::Success) {
         std::cerr << check.Failure() << "\n";
         return false;
     }
 
-    auto result = tint::hlsl::writer::Generate(ir.Get(), gen_options);
+    auto result = tint::hlsl::writer::Generate(ir, gen_options);
     if (result != tint::Success) {
-        tint::cmd::PrintWGSL(std::cerr, src_program);
+        options.printer->Print(tint::core::ir::Disassembler(ir).Text());
         std::cerr << "Failed to generate: " << result.Failure() << "\n";
         return false;
     }
@@ -1148,10 +1059,9 @@
         auto dxc = tint::Command::LookPath(dxc_path);
         if (dxc.Found()) {
             uint32_t hlsl_shader_model = options.hlsl_shader_model;
-            auto enable_list = src_program.AST().Enables();
             bool dxc_require_16bit_types = false;
-            for (auto* enable : enable_list) {
-                if (enable->HasExtension(tint::wgsl::Extension::kF16)) {
+            for (auto* ty : ir.Types()) {
+                if (ty->Is<tint::core::type::F16>()) {
                     dxc_require_16bit_types = true;
                     break;
                 }
@@ -1216,11 +1126,12 @@
 /// Generate GLSL code for a program.
 /// @param options the options that Tint was invoked with
 /// @param inspector the inspector
-/// @param src_program the program to generate
+/// @param ir the module to generate
 /// @returns true on success
+[[maybe_unused]]
 bool GenerateGlsl([[maybe_unused]] Options& options,
                   [[maybe_unused]] tint::inspector::Inspector& inspector,
-                  [[maybe_unused]] tint::Program& src_program) {
+                  [[maybe_unused]] tint::core::ir::Module& ir) {
 #if TINT_BUILD_GLSL_WRITER
     tint::glsl::writer::Options gen_options;
     gen_options.strip_all_names = options.rename_all;
@@ -1253,49 +1164,18 @@
         gen_options.bgra_swizzle_locations.insert({idx});
     }
 
-    // Convert the AST program to an IR module.
-    auto ir = tint::wgsl::reader::ProgramToLoweredIR(src_program);
-    if (ir != tint::Success) {
-        std::cerr << "Failed to generate IR: " << ir << "\n";
-        return false;
-    }
-
-    if (options.ep_name != "") {
-        auto singleEntryPointResult =
-            tint::core::ir::transform::SingleEntryPoint(ir.Get(), options.ep_name);
-        if (singleEntryPointResult != tint::Success) {
-            std::cerr << "Pipeline single entry point (IR) failed:\n"
-                      << singleEntryPointResult.Failure() << "\n";
-        }
-    }
-
-    // this needs to run after SingleEntryPoint transform which removes unused
-    // overrides for the current entry point.
-    auto substitute_override_cfg = CreateOverrideMap(options, inspector);
-    if (substitute_override_cfg != tint::Success) {
-        std::cerr << "Failed to create override map: " << substitute_override_cfg.Failure() << "\n";
-        return false;
-    }
-    auto substituteOverridesResult =
-        tint::core::ir::transform::SubstituteOverrides(ir.Get(), substitute_override_cfg.Get());
-    if (substituteOverridesResult != tint::Success) {
-        std::cerr << "Pipeline override substitution (IR) failed:\n"
-                  << substituteOverridesResult.Failure() << "\n";
-        return false;
-    }
-
     // Generate binding options.
-    gen_options.bindings = tint::glsl::writer::GenerateBindings(ir.Get());
+    gen_options.bindings = tint::glsl::writer::GenerateBindings(ir);
 
     // Check that the module and options are supported by the backend.
-    auto check = tint::glsl::writer::CanGenerate(ir.Get(), gen_options);
+    auto check = tint::glsl::writer::CanGenerate(ir, gen_options);
     if (check != tint::Success) {
         std::cerr << check.Failure() << "\n";
         return false;
     }
 
     // Generate GLSL.
-    auto result = tint::glsl::writer::Generate(ir.Get(), gen_options);
+    auto result = tint::glsl::writer::Generate(ir, gen_options);
     if (result != tint::Success) {
         std::cerr << "Failed to generate: " << result.Failure() << "\n";
         return false;
@@ -1437,18 +1317,54 @@
         return GenerateWgsl(options, inspector, info.program) ? 0 : 1;
     }
 
+#if TINT_BUILD_WGSL_READER
     auto generate = [&]() {
+        // Convert the AST program to an IR module.
+        auto ir = tint::wgsl::reader::ProgramToLoweredIR(info.program);
+        if (ir != tint::Success) {
+            std::cerr << "Failed to generate IR: " << ir << "\n";
+            return false;
+        }
+
+        // Strip the module down to a single entry point.
+        if (options.ep_name != "") {
+            auto singleEntryPointResult =
+                tint::core::ir::transform::SingleEntryPoint(ir.Get(), options.ep_name);
+            if (singleEntryPointResult != tint::Success) {
+                std::cerr << "SingleEntryPoint failed:\n"
+                          << singleEntryPointResult.Failure() << "\n";
+                return false;
+            }
+        }
+
+        // Run SubstituteOverrides to replace override instructions with constants.
+        // This needs to run after SingleEntryPoint which removes unused overrides for the target
+        // entry point.
+        auto substitute_override_cfg = CreateOverrideMap(options, inspector);
+        if (substitute_override_cfg != tint::Success) {
+            std::cerr << "Failed to create override map: " << substitute_override_cfg.Failure()
+                      << "\n";
+            return false;
+        }
+        auto substituteOverridesResult =
+            tint::core::ir::transform::SubstituteOverrides(ir.Get(), substitute_override_cfg.Get());
+        if (substituteOverridesResult != tint::Success) {
+            std::cerr << "SubstituteOverrides failed:\n"
+                      << substituteOverridesResult.Failure() << "\n";
+            return false;
+        }
+
         switch (options.format) {
             case Format::kSpirv:
             case Format::kSpvAsm:
-                return GenerateSpirv(options, inspector, info.program);
+                return GenerateSpirv(options, inspector, ir.Get());
             case Format::kMsl:
-                return GenerateMsl(options, inspector, info.program);
+                return GenerateMsl(options, inspector, ir.Get());
             case Format::kHlsl:
             case Format::kHlslFxc:
-                return GenerateHlsl(options, inspector, info.program);
+                return GenerateHlsl(options, inspector, ir.Get());
             case Format::kGlsl:
-                return GenerateGlsl(options, inspector, info.program);
+                return GenerateGlsl(options, inspector, ir.Get());
             case Format::kWgsl:
                 TINT_UNREACHABLE();
             case Format::kNone:
@@ -1507,6 +1423,10 @@
         }
     }
     return success ? 0 : 1;
+#else
+    std::cerr << "Cannot convert WGSL programs to Tint IR without the WGSL reader\n";
+    return false;
+#endif  // TINT_BUILD_WGSL_READER
 }
 
 /// Run a server that accepts arguments on stdin.