Add "hlsl-fxc" format to Tint executable

Treat "hlsl-fxc" as a separate format to "hlsl", producing HLSL for FXC,
while "hlsl" means produce HLSL for DXC. This is now used to set the
HLSL `Compiler` option accordingly.

Also, no longer validate HLSL with both DXC and FXC. If the format is
"hlsl-fxc", validate with FXC; if "hlsl", then validate with DXC. This
is simpler, and mirrors how it works for the other backends.

Regenerated Tint e2e tests with this change updates a few "ir.fxc.hlsl"
files now that they are generated using the FXC-enabled path.
Specifically, these run the FxcPolyfill transform, which implements a
workaround for default-only switch statements.

Bug: 369233346
Change-Id: Ic5ea8ee5e3c4a4c24accd5a4b3c6f82d29646d0f
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/208015
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: James Price <jrprice@google.com>
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index 0943a2f..e94bc08 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -151,6 +151,7 @@
     kWgsl,
     kMsl,
     kHlsl,
+    kHlslFxc,
     kGlsl,
     kIr,
 };
@@ -275,6 +276,7 @@
     WGSL_WRITER_ONLY(format_enum_names.Emplace(Format::kWgsl, "wgsl"));
     MSL_WRITER_ONLY(format_enum_names.Emplace(Format::kMsl, "msl"));
     HLSL_WRITER_ONLY(format_enum_names.Emplace(Format::kHlsl, "hlsl"));
+    HLSL_WRITER_ONLY(format_enum_names.Emplace(Format::kHlslFxc, "hlsl-fxc"));
     GLSL_WRITER_ONLY(format_enum_names.Emplace(Format::kGlsl, "glsl"));
     WGSL_READER_ONLY(format_enum_names.Emplace(Format::kIr, "ir"));
 
@@ -316,13 +318,13 @@
 #if TINT_BUILD_HLSL_WRITER
     auto& fxc_path =
         options.Add<StringOption>("fxc", R"(Path to FXC dll, used to validate HLSL output.
-When specified, automatically enables HLSL validation with FXC)",
+When specified, automatically enables HLSL validation)",
                                   Parameter{"path"});
     TINT_DEFER(opts->fxc_path = fxc_path.value.value_or(""));
 
     auto& dxc_path =
         options.Add<StringOption>("dxc", R"(Path to DXC dll, used to validate HLSL output.
-When specified, automatically enables HLSL validation with DXC)",
+When specified, automatically enables HLSL validation)",
                                   Parameter{"path"});
     TINT_DEFER(opts->dxc_path = dxc_path.value.value_or(""));
 #endif  // TINT_BUILD_HLSL_WRITER
@@ -912,11 +914,7 @@
 /// @returns true on success
 bool GenerateHlsl(const tint::Program& program, const Options& options) {
 #if TINT_BUILD_HLSL_WRITER
-    // If --fxc or --dxc was passed, then we must explicitly find and validate with that respective
-    // compiler.
-    const bool must_validate_dxc = !options.dxc_path.empty();
-    const bool must_validate_fxc = !options.fxc_path.empty();
-
+    const bool for_fxc = options.format == Format::kHlslFxc;
     // TODO(jrprice): Provide a way for the user to set non-default options.
     tint::hlsl::writer::Options gen_options;
     gen_options.disable_robustness = !options.enable_robustness;
@@ -927,6 +925,8 @@
     gen_options.polyfill_dot_4x8_packed = options.hlsl_shader_model < kMinShaderModelForDP4aInHLSL;
     gen_options.polyfill_pack_unpack_4x8 =
         options.hlsl_shader_model < kMinShaderModelForPackUnpack4x8InHLSL;
+    gen_options.compiler = for_fxc ? tint::hlsl::writer::Options::Compiler::kFXC
+                                   : tint::hlsl::writer::Options::Compiler::kDXC;
 
     tint::Result<tint::hlsl::writer::Output> result;
     if (options.use_ir) {
@@ -956,84 +956,74 @@
         PrintHash(hash);
     }
 
-    if ((options.validate || must_validate_dxc || must_validate_fxc) &&
-        (options.skip_hash.count(hash) == 0)) {
+    const bool validate =
+        (options.validate || !options.fxc_path.empty() || !options.dxc_path.empty()) &&
+        (options.skip_hash.count(hash) == 0);
+
+    if (validate && !for_fxc) {
+        // DXC validation
         tint::hlsl::validate::Result dxc_res;
-        bool dxc_found = false;
-        if (options.validate || must_validate_dxc) {
-            auto dxc =
-                tint::Command::LookPath(options.dxc_path.empty() ? tint::hlsl::validate::kDxcDLLName
-                                                                 : std::string(options.dxc_path));
-            if (dxc.Found()) {
-                dxc_found = true;
-
-                uint32_t hlsl_shader_model = options.hlsl_shader_model;
-                auto enable_list = program.AST().Enables();
-                bool dxc_require_16bit_types = false;
-                for (auto* enable : enable_list) {
-                    if (enable->HasExtension(tint::wgsl::Extension::kF16)) {
-                        dxc_require_16bit_types = true;
-                        break;
-                    }
+        const std::string dxc_path =
+            options.dxc_path.empty() ? tint::hlsl::validate::kDxcDLLName : options.dxc_path;
+        auto dxc = tint::Command::LookPath(dxc_path);
+        if (dxc.Found()) {
+            uint32_t hlsl_shader_model = options.hlsl_shader_model;
+            auto enable_list = program.AST().Enables();
+            bool dxc_require_16bit_types = false;
+            for (auto* enable : enable_list) {
+                if (enable->HasExtension(tint::wgsl::Extension::kF16)) {
+                    dxc_require_16bit_types = true;
+                    break;
                 }
-
-                dxc_res = tint::hlsl::validate::ValidateUsingDXC(
-                    dxc.Path(), result->hlsl, result->entry_points, dxc_require_16bit_types,
-                    hlsl_shader_model);
-            } else if (must_validate_dxc) {
-                // DXC was explicitly requested. Error if it could not be found.
-                dxc_res.failed = true;
-                dxc_res.output = "DXC executable '" + std::string(options.dxc_path) +
-                                 "' not found. Cannot validate";
             }
+            if (options.verbose) {
+                std::cout << "Validating with DXC: " << dxc.Path() << "\n";
+            }
+            dxc_res = tint::hlsl::validate::ValidateUsingDXC(
+                dxc.Path(), result->hlsl, result->entry_points, dxc_require_16bit_types,
+                hlsl_shader_model);
+        } else {
+            dxc_res.failed = true;
+            dxc_res.output = "DXC executable '" + dxc_path + "' not found. Cannot validate.";
         }
 
-        tint::hlsl::validate::Result fxc_res;
-        bool fxc_found = false;
-        if (options.validate || must_validate_fxc) {
-            auto fxc =
-                tint::Command::LookPath(options.fxc_path.empty() ? tint::hlsl::validate::kFxcDLLName
-                                                                 : std::string(options.fxc_path));
+        if (dxc_res.failed) {
+            std::cerr << "DXC validation failure:\n" << dxc_res.output << "\n";
+            return false;
+        }
+        if (options.verbose) {
+            std::cout << "Passed DXC validation. Compiler output:\n" << dxc_res.output << "\n";
+        }
+    }
 
-#ifdef _WIN32
-            if (fxc.Found()) {
-                fxc_found = true;
-                fxc_res = tint::hlsl::validate::ValidateUsingFXC(fxc.Path(), result->hlsl,
-                                                                 result->entry_points);
-            } else if (must_validate_fxc) {
-                // FXC was explicitly requested. Error if it could not be found.
-                fxc_res.failed = true;
-                fxc_res.output = "FXC DLL '" + options.fxc_path + "' not found. Cannot validate";
-            }
+    if (validate && for_fxc) {
+        // FXC validation
+#ifndef _WIN32
+        std::cerr << "FXC can only be used on Windows.\n";
+        return false;
 #else
-            if (must_validate_fxc) {
-                fxc_res.failed = true;
-                fxc_res.output = "FXC can only be used on Windows.";
+        tint::hlsl::validate::Result fxc_res;
+        auto fxc = tint::Command::LookPath(
+            options.fxc_path.empty() ? tint::hlsl::validate::kFxcDLLName : options.fxc_path);
+        if (fxc.Found()) {
+            if (options.verbose) {
+                std::cout << "Validating with FXC: " << fxc.Path() << "\n";
             }
-#endif  // _WIN32
+            fxc_res = tint::hlsl::validate::ValidateUsingFXC(fxc.Path(), result->hlsl,
+                                                             result->entry_points);
+        } else {
+            fxc_res.failed = true;
+            fxc_res.output = "FXC DLL '" + options.fxc_path + "' not found. Cannot validate.";
         }
 
         if (fxc_res.failed) {
             std::cerr << "FXC validation failure:\n" << fxc_res.output << "\n";
-        }
-        if (dxc_res.failed) {
-            std::cerr << "DXC validation failure:\n" << dxc_res.output << "\n";
-        }
-        if (fxc_res.failed || dxc_res.failed) {
-            return false;
-        }
-        if (!fxc_found && !dxc_found) {
-            std::cerr << "Couldn't find FXC or DXC. Cannot validate\n";
             return false;
         }
         if (options.verbose) {
-            if (fxc_found && !fxc_res.failed) {
-                std::cout << "Passed FXC validation\n" << fxc_res.output << "\n";
-            }
-            if (dxc_found && !dxc_res.failed) {
-                std::cout << "Passed DXC validation\n" << dxc_res.output << "\n";
-            }
+            std::cout << "Passed FXC validation. Compiler output:\n" << fxc_res.output << "\n";
         }
+#endif  // _WIN32
     }
 
     return true;
@@ -1382,7 +1372,8 @@
             break;
         }
 #endif  // TINT_BUILD_GLSL_WRITER
-        case Format::kHlsl: {
+        case Format::kHlsl:
+        case Format::kHlslFxc: {
 #if TINT_BUILD_HLSL_WRITER
             transform_inputs.Add<tint::ast::transform::Renamer::Config>(
                 options.rename_all ? tint::ast::transform::Renamer::Target::kAll
@@ -1454,6 +1445,7 @@
             success = GenerateMsl(program, options);
             break;
         case Format::kHlsl:
+        case Format::kHlslFxc:
             success = GenerateHlsl(program, options);
             break;
         case Format::kGlsl:
diff --git a/test/tint/bug/tint/1820.wgsl.expected.ir.fxc.hlsl b/test/tint/bug/tint/1820.wgsl.expected.ir.fxc.hlsl
index 902399a..3cdb20b 100644
--- a/test/tint/bug/tint/1820.wgsl.expected.ir.fxc.hlsl
+++ b/test/tint/bug/tint/1820.wgsl.expected.ir.fxc.hlsl
@@ -7,6 +7,7 @@
 void foo(float x) {
   switch(tint_f32_to_i32(x)) {
     default:
+    case int(0):
     {
       break;
     }
@@ -21,6 +22,7 @@
 void bar(float x) {
   switch(baz(tint_f32_to_i32(x))) {
     default:
+    case int(0):
     {
       break;
     }
diff --git a/test/tint/diagnostic_filtering/default_case_body_attribute.wgsl.expected.ir.fxc.hlsl b/test/tint/diagnostic_filtering/default_case_body_attribute.wgsl.expected.ir.fxc.hlsl
index 05890bf..b98ea35 100644
--- a/test/tint/diagnostic_filtering/default_case_body_attribute.wgsl.expected.ir.fxc.hlsl
+++ b/test/tint/diagnostic_filtering/default_case_body_attribute.wgsl.expected.ir.fxc.hlsl
@@ -24,6 +24,7 @@
 void main_inner(float x) {
   switch(tint_f32_to_i32(x)) {
     default:
+    case int(0):
     {
       t.Sample(s, (0.0f).xx);
       break;
diff --git a/test/tint/diagnostic_filtering/switch_body_attribute.wgsl.expected.ir.fxc.hlsl b/test/tint/diagnostic_filtering/switch_body_attribute.wgsl.expected.ir.fxc.hlsl
index 4781c34..da10aa1 100644
--- a/test/tint/diagnostic_filtering/switch_body_attribute.wgsl.expected.ir.fxc.hlsl
+++ b/test/tint/diagnostic_filtering/switch_body_attribute.wgsl.expected.ir.fxc.hlsl
@@ -22,6 +22,7 @@
 void main_inner(float x) {
   switch(tint_f32_to_i32(x)) {
     default:
+    case int(0):
     {
       ddx(1.0f);
       break;
diff --git a/test/tint/diagnostic_filtering/switch_statement_attribute.wgsl.expected.ir.fxc.hlsl b/test/tint/diagnostic_filtering/switch_statement_attribute.wgsl.expected.ir.fxc.hlsl
index fef385b..a67af25 100644
--- a/test/tint/diagnostic_filtering/switch_statement_attribute.wgsl.expected.ir.fxc.hlsl
+++ b/test/tint/diagnostic_filtering/switch_statement_attribute.wgsl.expected.ir.fxc.hlsl
@@ -26,6 +26,7 @@
   }
   switch(int(v)) {
     default:
+    case int(0):
     {
       break;
     }
diff --git a/test/tint/statements/switch/only_default_case.wgsl.expected.ir.fxc.hlsl b/test/tint/statements/switch/only_default_case.wgsl.expected.ir.fxc.hlsl
index 61b4e6a..10d50ac 100644
--- a/test/tint/statements/switch/only_default_case.wgsl.expected.ir.fxc.hlsl
+++ b/test/tint/statements/switch/only_default_case.wgsl.expected.ir.fxc.hlsl
@@ -5,6 +5,7 @@
   int result = int(0);
   switch(i) {
     default:
+    case int(0):
     {
       result = int(44);
       break;
diff --git a/test/tint/switch/switch_only_default.wgsl.expected.ir.fxc.hlsl b/test/tint/switch/switch_only_default.wgsl.expected.ir.fxc.hlsl
index aa7bd20..5986b3a 100644
--- a/test/tint/switch/switch_only_default.wgsl.expected.ir.fxc.hlsl
+++ b/test/tint/switch/switch_only_default.wgsl.expected.ir.fxc.hlsl
@@ -3,6 +3,7 @@
   int a_1 = int(0);
   switch(a_1) {
     default:
+    case int(0):
     {
       return;
     }
diff --git a/tools/src/cmd/tests/main.go b/tools/src/cmd/tests/main.go
index 7de9b9e..73dc412 100644
--- a/tools/src/cmd/tests/main.go
+++ b/tools/src/cmd/tests/main.go
@@ -711,9 +711,15 @@
 
 		expected = strings.ReplaceAll(expected, "\r\n", "\n")
 
+		outputFormat := strings.Split(string(j.format), "-")[0] // 'hlsl-fxc-ir' -> 'hlsl', etc.
+		if j.format == hlslFXC || j.format == hlslFXCIR {
+			// Emit HLSL specifically for FXC
+			outputFormat += "-fxc"
+		}
+
 		args := []string{
 			j.file,
-			"--format", strings.Split(string(j.format), "-")[0], // 'hlsl-fxc' -> 'hlsl', etc.
+			"--format", outputFormat,
 			"--print-hash",
 		}