hlsl ast fuzzer: validate hlsl with dxc

Bug: tint:2234
Change-Id: I571b227db8b4c1d35e66aa2b44d6d02656a1c83b
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/186560
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/cmd/fuzz/wgsl/fuzz.h b/src/tint/cmd/fuzz/wgsl/fuzz.h
index 4533e5a..d128695 100644
--- a/src/tint/cmd/fuzz/wgsl/fuzz.h
+++ b/src/tint/cmd/fuzz/wgsl/fuzz.h
@@ -48,6 +48,9 @@
     bool run_concurrently = false;
     /// If true, print the fuzzer name to stdout before running.
     bool verbose = false;
+    /// If not empty, load DXC from this path when fuzzing HLSL generation, and fail the fuzzer if
+    /// not found, or if DXC fails to compile.
+    std::string dxc;
 };
 
 /// ProgramFuzzer describes a fuzzer function that takes a WGSL program as input
diff --git a/src/tint/cmd/fuzz/wgsl/main_fuzz.cc b/src/tint/cmd/fuzz/wgsl/main_fuzz.cc
index 2608906..06489f1 100644
--- a/src/tint/cmd/fuzz/wgsl/main_fuzz.cc
+++ b/src/tint/cmd/fuzz/wgsl/main_fuzz.cc
@@ -26,6 +26,8 @@
 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 
 #include <iostream>
+#include <string>
+#include <unordered_map>
 
 #include "src/tint/cmd/fuzz/wgsl/fuzz.h"
 #include "src/tint/utils/cli/cli.h"
@@ -76,6 +78,7 @@
         opts.Add<tint::cli::BoolOption>("concurrent", "runs the fuzzers concurrently");
     auto& opt_verbose =
         opts.Add<tint::cli::BoolOption>("verbose", "prints the name of each fuzzer before running");
+    auto& opt_dxc = opts.Add<tint::cli::StringOption>("dxc", "path to DXC DLL");
 
     tint::cli::ParseOptions parse_opts;
     parse_opts.ignore_unknown = true;
@@ -93,5 +96,6 @@
     options.filter = opt_filter.value.value_or("");
     options.run_concurrently = opt_concurrent.value.value_or(false);
     options.verbose = opt_verbose.value.value_or(false);
+    options.dxc = opt_dxc.value.value_or("");
     return 0;
 }
diff --git a/src/tint/lang/hlsl/writer/BUILD.cmake b/src/tint/lang/hlsl/writer/BUILD.cmake
index a40f368..67af1a7 100644
--- a/src/tint/lang/hlsl/writer/BUILD.cmake
+++ b/src/tint/lang/hlsl/writer/BUILD.cmake
@@ -159,6 +159,7 @@
   tint_lang_wgsl_program
   tint_lang_wgsl_sem
   tint_utils_bytes
+  tint_utils_command
   tint_utils_containers
   tint_utils_diagnostic
   tint_utils_ice
@@ -176,6 +177,7 @@
 
 if(TINT_BUILD_HLSL_WRITER)
   tint_target_add_dependencies(tint_lang_hlsl_writer_fuzz fuzz
+    tint_lang_hlsl_validate
     tint_lang_hlsl_writer
   )
 endif(TINT_BUILD_HLSL_WRITER)
diff --git a/src/tint/lang/hlsl/writer/BUILD.gn b/src/tint/lang/hlsl/writer/BUILD.gn
index 4c8f704..2111f1c 100644
--- a/src/tint/lang/hlsl/writer/BUILD.gn
+++ b/src/tint/lang/hlsl/writer/BUILD.gn
@@ -140,6 +140,7 @@
       "${tint_src_dir}/lang/wgsl/program",
       "${tint_src_dir}/lang/wgsl/sem",
       "${tint_src_dir}/utils/bytes",
+      "${tint_src_dir}/utils/command",
       "${tint_src_dir}/utils/containers",
       "${tint_src_dir}/utils/diagnostic",
       "${tint_src_dir}/utils/ice",
@@ -156,7 +157,10 @@
     ]
 
     if (tint_build_hlsl_writer) {
-      deps += [ "${tint_src_dir}/lang/hlsl/writer" ]
+      deps += [
+        "${tint_src_dir}/lang/hlsl/validate",
+        "${tint_src_dir}/lang/hlsl/writer",
+      ]
     }
 
     if (tint_build_wgsl_reader) {
diff --git a/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc b/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc
index a2865f9..12497e6 100644
--- a/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc
+++ b/src/tint/lang/hlsl/writer/writer_ast_fuzz.cc
@@ -27,19 +27,58 @@
 
 // GEN_BUILD:CONDITION(tint_build_wgsl_reader)
 
+#include <string>
+#include <unordered_map>
+
 #include "src/tint/cmd/fuzz/wgsl/fuzz.h"
+#include "src/tint/lang/hlsl/validate/validate.h"
 #include "src/tint/lang/hlsl/writer/writer.h"
 #include "src/tint/lang/wgsl/ast/module.h"
+#include "src/tint/utils/command/command.h"
 
 namespace tint::hlsl::writer {
 namespace {
 
-void ASTFuzzer(const tint::Program& program, Options options) {
+void ASTFuzzer(const tint::Program& program,
+               const fuzz::wgsl::Options& fuzz_options,
+               Options options) {
     if (program.AST().HasOverrides()) {
         return;
     }
 
-    [[maybe_unused]] auto res = tint::hlsl::writer::Generate(program, options);
+    auto res = tint::hlsl::writer::Generate(program, options);
+    if (res == Success) {
+        const char* dxc_path = validate::kDxcDLLName;
+        bool must_validate = false;
+        if (!fuzz_options.dxc.empty()) {
+            must_validate = true;
+            dxc_path = fuzz_options.dxc.c_str();
+        }
+
+        auto dxc = tint::Command::LookPath(dxc_path);
+        if (dxc.Found()) {
+            uint32_t hlsl_shader_model = 60;
+            bool require_16bit_types = false;
+            auto enable_list = program.AST().Enables();
+            for (auto* enable : enable_list) {
+                if (enable->HasExtension(tint::wgsl::Extension::kF16)) {
+                    hlsl_shader_model = 62;
+                    require_16bit_types = true;
+                    break;
+                }
+            }
+
+            auto validate_res = validate::ValidateUsingDXC(dxc.Path(), res->hlsl, res->entry_points,
+                                                           require_16bit_types, hlsl_shader_model);
+
+            if (must_validate && validate_res.failed) {
+                TINT_ICE() << "DXC was expected to succeed, but failed: " << validate_res.output;
+            }
+
+        } else if (must_validate) {
+            TINT_ICE() << "DXC path was explicitly specified, but was not found: " << dxc_path;
+        }
+    }
 }
 
 }  // namespace