tint_cmd: use DXC via shared library (dxcompiler) rather than executable (dxc)

Bug: tint:2234
Change-Id: I90386392f55970c19723365e707a8a43293e16ff
Reviewed-on: https://dawn-review.googlesource.com/c/dawn/+/185902
Commit-Queue: Antonio Maiorano <amaiorano@google.com>
Reviewed-by: Ryan Harrison <rharrison@chromium.org>
Reviewed-by: Ben Clayton <bclayton@google.com>
diff --git a/src/tint/BUILD.gn b/src/tint/BUILD.gn
index f01ff5e..b1e1a64 100644
--- a/src/tint/BUILD.gn
+++ b/src/tint/BUILD.gn
@@ -252,6 +252,13 @@
   libs = [ "ws2_32.lib" ]
 }
 
+config("dxc-include-config") {
+  include_dirs = [ "${dawn_dxc_dir}/include" ]
+}
+source_set("dxc-include") {
+  public_configs = [ ":dxc-include-config" ]
+}
+
 ###############################################################################
 # Fuzzers
 ###############################################################################
diff --git a/src/tint/CMakeLists.txt b/src/tint/CMakeLists.txt
index 235349c..6969216 100644
--- a/src/tint/CMakeLists.txt
+++ b/src/tint/CMakeLists.txt
@@ -501,6 +501,8 @@
       target_link_libraries(${TARGET} PRIVATE
         absl_strings
       )
+    elseif(${DEPENDENCY} STREQUAL "dxc-include")
+      target_include_directories(${TARGET} PRIVATE "${DAWN_THIRD_PARTY_DIR}/dxc/include")
     elseif(${DEPENDENCY} STREQUAL "jsoncpp")
       target_link_libraries(${TARGET} PRIVATE jsoncpp_static)
     elseif(${DEPENDENCY} STREQUAL "langsvr")
diff --git a/src/tint/cmd/tint/main.cc b/src/tint/cmd/tint/main.cc
index 24ef2fd..68a9d81 100644
--- a/src/tint/cmd/tint/main.cc
+++ b/src/tint/cmd/tint/main.cc
@@ -1009,8 +1009,9 @@
         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() ? "dxc" : std::string(options.dxc_path));
+            auto dxc =
+                tint::Command::LookPath(options.dxc_path.empty() ? tint::hlsl::validate::kDxcDLLName
+                                                                 : std::string(options.dxc_path));
             if (dxc.Found()) {
                 dxc_found = true;
 
@@ -1053,7 +1054,7 @@
                 fxc_res.output = "FXC DLL '" + options.fxc_path + "' not found. Cannot validate";
             }
 #else
-            if (must_validate_dxc) {
+            if (must_validate_fxc) {
                 fxc_res.failed = true;
                 fxc_res.output = "FXC can only be used on Windows.";
             }
diff --git a/src/tint/externals.json b/src/tint/externals.json
index dbdf96d..03048a5 100644
--- a/src/tint/externals.json
+++ b/src/tint/externals.json
@@ -6,7 +6,13 @@
     "abseil": {
         "IncludePatterns": [
             "absl/**"
+        ]
+    },
+    "dxc-include": {
+        "IncludePatterns": [
+            "dxc/**"
         ],
+        "Condition": "tint_build_hlsl_writer"
     },
     "google-benchmark": {
         "IncludePatterns": [
diff --git a/src/tint/lang/hlsl/validate/BUILD.bazel b/src/tint/lang/hlsl/validate/BUILD.bazel
index 512dfd0..e045855 100644
--- a/src/tint/lang/hlsl/validate/BUILD.bazel
+++ b/src/tint/lang/hlsl/validate/BUILD.bazel
@@ -56,7 +56,12 @@
     "//src/tint/utils/rtti",
     "//src/tint/utils/text",
     "//src/tint/utils/traits",
-  ],
+  ] + select({
+    ":tint_build_hlsl_writer": [
+      
+    ],
+    "//conditions:default": [],
+  }),
   copts = COPTS,
   visibility = ["//visibility:public"],
 )
diff --git a/src/tint/lang/hlsl/validate/BUILD.cmake b/src/tint/lang/hlsl/validate/BUILD.cmake
index a16018e..5aded7f 100644
--- a/src/tint/lang/hlsl/validate/BUILD.cmake
+++ b/src/tint/lang/hlsl/validate/BUILD.cmake
@@ -59,4 +59,10 @@
   tint_utils_traits
 )
 
+if(TINT_BUILD_HLSL_WRITER)
+  tint_target_add_external_dependencies(tint_lang_hlsl_validate lib
+    "dxc-include"
+  )
+endif(TINT_BUILD_HLSL_WRITER)
+
 endif(TINT_BUILD_HLSL_WRITER)
\ No newline at end of file
diff --git a/src/tint/lang/hlsl/validate/BUILD.gn b/src/tint/lang/hlsl/validate/BUILD.gn
index 44d66c5..3143d42 100644
--- a/src/tint/lang/hlsl/validate/BUILD.gn
+++ b/src/tint/lang/hlsl/validate/BUILD.gn
@@ -56,5 +56,9 @@
       "${tint_src_dir}/utils/text",
       "${tint_src_dir}/utils/traits",
     ]
+
+    if (tint_build_hlsl_writer) {
+      deps += [ "${tint_src_dir}:dxc-include" ]
+    }
   }
 }
diff --git a/src/tint/lang/hlsl/validate/validate.cc b/src/tint/lang/hlsl/validate/validate.cc
index cfca843..027756f 100644
--- a/src/tint/lang/hlsl/validate/validate.cc
+++ b/src/tint/lang/hlsl/validate/validate.cc
@@ -31,17 +31,32 @@
 
 #include "src/tint/utils/command/command.h"
 #include "src/tint/utils/file/tmpfile.h"
+#include "src/tint/utils/macros/defer.h"
 #include "src/tint/utils/text/string.h"
 
 #ifdef _WIN32
 #include <Windows.h>
+#include <atlbase.h>
 #include <d3dcommon.h>
 #include <d3dcompiler.h>
-
 #include <wrl.h>
-using Microsoft::WRL::ComPtr;
+#else
+#include <dlfcn.h>
 #endif  // _WIN32
 
+// dxc headers
+TINT_BEGIN_DISABLE_ALL_WARNINGS();
+#ifdef __clang__
+// # Use UUID emulation with clang to avoid compiling with ms-extensions
+#define __EMULATE_UUID
+#endif
+#include "dxc/dxcapi.h"
+TINT_END_DISABLE_ALL_WARNINGS();
+
+// Disable warnings about old-style casts which result from using
+// the SUCCEEDED and FAILED macros that C-style cast to HRESULT.
+TINT_DISABLE_WARNING_OLD_STYLE_CAST
+
 namespace tint::hlsl::validate {
 
 Result ValidateUsingDXC(const std::string& dxc_path,
@@ -51,9 +66,8 @@
                         uint32_t hlsl_shader_model) {
     Result result;
 
-    auto dxc = tint::Command(dxc_path);
-    if (!dxc.Found()) {
-        result.output = "DXC not found at '" + std::string(dxc_path) + "'";
+    if (entry_points.empty()) {
+        result.output = "No entrypoint found";
         result.failed = true;
         return result;
     }
@@ -70,64 +84,140 @@
         result.failed = true;
         return result;
     }
-    std::string shader_model_version =
-        std::to_string(hlsl_shader_model / 10) + "_" + std::to_string(hlsl_shader_model % 10);
 
-    tint::TmpFile file;
-    file << source;
+#define CHECK_HR(hr, error_msg)        \
+    do {                               \
+        if (FAILED(hr)) {              \
+            result.output = error_msg; \
+            result.failed = true;      \
+            return result;             \
+        }                              \
+    } while (false)
+
+    HRESULT hr;
+
+    // Load the dll and get the DxcCreateInstance function
+    using PFN_DXC_CREATE_INSTANCE =
+        HRESULT(__stdcall*)(REFCLSID rclsid, REFIID riid, LPVOID * ppCompiler);
+    PFN_DXC_CREATE_INSTANCE dxc_create_instance = nullptr;
+#ifdef _WIN32
+    HMODULE dxcLib = LoadLibraryA(dxc_path.c_str());
+    if (dxcLib == nullptr) {
+        result.output = "Failed to load dxc: " + dxc_path;
+        result.failed = true;
+        return result;
+    }
+    // Avoid ASAN false positives when unloading DLL: https://github.com/google/sanitizers/issues/89
+#if !defined(TINT_ASAN_ENABLED)
+    TINT_DEFER({ FreeLibrary(dxcLib); });
+#endif
+
+    dxc_create_instance =
+        reinterpret_cast<PFN_DXC_CREATE_INSTANCE>(GetProcAddress(dxcLib, "DxcCreateInstance"));
+#else
+    void* dxcLib = dlopen(dxc_path.c_str(), RTLD_LAZY | RTLD_GLOBAL);
+    if (dxcLib == nullptr) {
+        result.output = "Failed to load dxc: " + dxc_path;
+        result.failed = true;
+        return result;
+    }
+    // Avoid ASAN false positives when unloading DLL: https://github.com/google/sanitizers/issues/89
+#if !defined(TINT_ASAN_ENABLED)
+    TINT_DEFER({ dlclose(dxcLib); });
+#endif
+
+    dxc_create_instance =
+        reinterpret_cast<PFN_DXC_CREATE_INSTANCE>(dlsym(dxcLib, "DxcCreateInstance"));
+#endif
+    if (dxc_create_instance == nullptr) {
+        result.output = "GetProcAccess failed";
+        result.failed = true;
+        return result;
+    }
+
+    CComPtr<IDxcCompiler3> dxc_compiler;
+    hr = dxc_create_instance(CLSID_DxcCompiler, IID_PPV_ARGS(&dxc_compiler));
+    CHECK_HR(hr, "DxcCreateInstance failed");
 
     for (auto ep : entry_points) {
-        const char* stage_prefix = "";
-
+        const wchar_t* stage_prefix = L"";
         switch (ep.second) {
             case ast::PipelineStage::kNone:
                 result.output = "Invalid PipelineStage";
                 result.failed = true;
                 return result;
             case ast::PipelineStage::kVertex:
-                stage_prefix = "vs";
+                stage_prefix = L"vs";
                 break;
             case ast::PipelineStage::kFragment:
-                stage_prefix = "ps";
+                stage_prefix = L"ps";
                 break;
             case ast::PipelineStage::kCompute:
-                stage_prefix = "cs";
+                stage_prefix = L"cs";
                 break;
         }
 
         // Match Dawn's compile flags
         // See dawn\src\dawn_native\d3d12\RenderPipelineD3D12.cpp
         // and dawn_native\d3d\ShaderUtils.cpp (GetDXCArguments)
-        auto res =
-            dxc("-T " + std::string(stage_prefix) + "_" + shader_model_version,  // Profile
-                "-HV 2018",                                                      // Use HLSL 2018
-                "-E " + ep.first,                                                // Entry point
-                "/Zpr",  // D3DCOMPILE_PACK_MATRIX_ROW_MAJOR
-                "/Gis",  // D3DCOMPILE_IEEE_STRICTNESS
-                require_16bit_types ? "-enable-16bit-types" : "",  // Enable 16-bit if required
-                file.Path());
-        if (!res.out.empty()) {
-            if (!result.output.empty()) {
-                result.output += "\n";
-            }
-            result.output += res.out;
-        }
-        if (!res.err.empty()) {
-            if (!result.output.empty()) {
-                result.output += "\n";
-            }
-            result.output += res.err;
-        }
-        result.failed = (res.error_code != 0);
+        std::wstring shader_model_version = std::to_wstring(hlsl_shader_model / 10) + L"_" +
+                                            std::to_wstring(hlsl_shader_model % 10);
+        std::wstring profile = std::wstring(stage_prefix) + L"_" + shader_model_version;
+        std::wstring entry_point = std::wstring(ep.first.begin(), ep.first.end());
+        std::vector<const wchar_t*> args{
+            L"-T",                                              // Profile
+            profile.c_str(),                                    //
+            L"-HV 2018",                                        // Use HLSL 2018
+            L"-E",                                              // Entry point
+            entry_point.c_str(),                                //
+            L"/Zpr",                                            // D3DCOMPILE_PACK_MATRIX_ROW_MAJOR
+            L"/Gis",                                            // D3DCOMPILE_IEEE_STRICTNESS
+            require_16bit_types ? L"-enable-16bit-types" : L""  // Enable 16-bit if required
+        };
 
-        // Remove the temporary file name from the output to keep output deterministic
-        result.output = tint::ReplaceAll(result.output, file.Path(), "shader.hlsl");
-    }
+        DxcBuffer source_buffer;
+        source_buffer.Ptr = source.c_str();
+        source_buffer.Size = source.length();
+        source_buffer.Encoding = DXC_CP_UTF8;
+        CComPtr<IDxcResult> compile_result;
+        hr = dxc_compiler->Compile(&source_buffer, args.data(), static_cast<UINT32>(args.size()),
+                                   nullptr, IID_PPV_ARGS(&compile_result));
+        CHECK_HR(hr, "Compile call failed");
 
-    if (entry_points.empty()) {
-        result.output = "No entrypoint found";
-        result.failed = true;
-        return result;
+        HRESULT compile_status;
+        hr = compile_result->GetStatus(&compile_status);
+        CHECK_HR(hr, "GetStatus call failed");
+
+        if (FAILED(compile_status)) {
+            CComPtr<IDxcBlobEncoding> errors;
+            hr = compile_result->GetErrorBuffer(&errors);
+            CHECK_HR(hr, "GetErrorBuffer call failed");
+            result.output = static_cast<char*>(errors->GetBufferPointer());
+            result.failed = true;
+            return result;
+        }
+
+        // Compilation succeeded, get compiled shader blob and disassamble it
+        CComPtr<IDxcBlob> compiled_shader;
+        hr = compile_result->GetResult(&compiled_shader);
+        CHECK_HR(hr, "GetResult call failed");
+
+        DxcBuffer compiled_shader_buffer;
+        compiled_shader_buffer.Ptr = compiled_shader->GetBufferPointer();
+        compiled_shader_buffer.Size = compiled_shader->GetBufferSize();
+        compiled_shader_buffer.Encoding = DXC_CP_UTF8;
+        CComPtr<IDxcResult> dis_result;
+        hr = dxc_compiler->Disassemble(&compiled_shader_buffer, IID_PPV_ARGS(&dis_result));
+        CHECK_HR(hr, "Disassemble call failed");
+
+        CComPtr<IDxcBlobEncoding> disassembly;
+        if (dis_result && dis_result->HasOutput(DXC_OUT_DISASSEMBLY) &&
+            SUCCEEDED(
+                dis_result->GetOutput(DXC_OUT_DISASSEMBLY, IID_PPV_ARGS(&disassembly), nullptr))) {
+            result.output = static_cast<char*>(disassembly->GetBufferPointer());
+        } else {
+            result.output = "Failed to disassemble shader";
+        }
     }
 
     return result;
@@ -139,6 +229,12 @@
                         const EntryPointList& entry_points) {
     Result result;
 
+    if (entry_points.empty()) {
+        result.output = "No entrypoint found";
+        result.failed = true;
+        return result;
+    }
+
     // This library leaks if an error happens in this function, but it is ok
     // because it is loaded at most once, and the executables using UsingFXC
     // are short-lived.
@@ -188,8 +284,8 @@
         UINT compileFlags = D3DCOMPILE_OPTIMIZATION_LEVEL0 | D3DCOMPILE_PACK_MATRIX_ROW_MAJOR |
                             D3DCOMPILE_IEEE_STRICTNESS;
 
-        ComPtr<ID3DBlob> compiledShader;
-        ComPtr<ID3DBlob> errors;
+        CComPtr<ID3DBlob> compiledShader;
+        CComPtr<ID3DBlob> errors;
         HRESULT res = d3dCompile(source.c_str(),    // pSrcData
                                  source.length(),   // SrcDataSize
                                  nullptr,           // pSourceName
@@ -206,11 +302,11 @@
             result.failed = true;
             return result;
         } else {
-            ComPtr<ID3DBlob> disassembly;
+            CComPtr<ID3DBlob> disassembly;
             res = d3dDisassemble(compiledShader->GetBufferPointer(),
                                  compiledShader->GetBufferSize(), 0, "", &disassembly);
             if (FAILED(res)) {
-                result.output = "failed to disassemble shader";
+                result.output = "Failed to disassemble shader";
             } else {
                 result.output = static_cast<char*>(disassembly->GetBufferPointer());
             }
@@ -219,12 +315,6 @@
 
     FreeLibrary(fxcLib);
 
-    if (entry_points.empty()) {
-        result.output = "No entrypoint found";
-        result.failed = true;
-        return result;
-    }
-
     return result;
 }
 #endif  // _WIN32
diff --git a/src/tint/lang/hlsl/validate/validate.h b/src/tint/lang/hlsl/validate/validate.h
index 8efb4fb..67ce25e 100644
--- a/src/tint/lang/hlsl/validate/validate.h
+++ b/src/tint/lang/hlsl/validate/validate.h
@@ -46,6 +46,16 @@
 /// Name of the FXC compiler DLL
 static constexpr const char kFxcDLLName[] = "d3dcompiler_47.dll";
 
+#if TINT_BUILD_IS_WIN
+static constexpr const char* kDxcDLLName = "dxcompiler.dll";
+#elif TINT_BUILD_IS_LINUX
+static constexpr const char* kDxcDLLName = "libdxcompiler.so";
+#elif TINT_BUILD_IS_MAC
+static constexpr const char* kDxcDLLName = "libdxcompiler.dylib";
+#else
+static constexpr const char* kDxcDLLName = "Invalid";
+#endif
+
 /// The return structure of Validate()
 struct Result {
     /// True if validation passed
diff --git a/src/tint/utils/macros/compiler.h b/src/tint/utils/macros/compiler.h
index a3de76f..ff60f3f0 100644
--- a/src/tint/utils/macros/compiler.h
+++ b/src/tint/utils/macros/compiler.h
@@ -54,6 +54,10 @@
 #define TINT_DISABLE_WARNING_ZERO_AS_NULLPTR             /* currently no-op */
 #define TINT_DISABLE_WARNING_MISSING_DESTRUCTOR_OVERRIDE /* currently no-op */
 
+#define TINT_BEGIN_DISABLE_ALL_WARNINGS() __pragma(warning(push, 0)) TINT_REQUIRE_SEMICOLON
+
+#define TINT_END_DISABLE_ALL_WARNINGS() __pragma(warning(pop)) TINT_REQUIRE_SEMICOLON
+
 // clang-format off
 #define TINT_BEGIN_DISABLE_WARNING(name)     \
     __pragma(warning(push))                  \
@@ -71,6 +75,10 @@
 #define TINT_UNLIKELY(x) x /* currently no-op */
 #define TINT_LIKELY(x) x   /* currently no-op */
 
+#if defined(__SANITIZE_ADDRESS__)
+#define TINT_ASAN_ENABLED
+#endif
+
 #elif defined(__clang__)
 ////////////////////////////////////////////////////////////////////////////////
 // Clang
@@ -123,6 +131,15 @@
     _Pragma("clang diagnostic pop")          \
     TINT_REQUIRE_SEMICOLON
 
+#define TINT_BEGIN_DISABLE_ALL_WARNINGS() \
+    _Pragma("clang diagnostic push")      \
+    _Pragma("clang diagnostic ignored \"-Weverything\"")       \
+    TINT_REQUIRE_SEMICOLON
+
+#define TINT_END_DISABLE_ALL_WARNINGS() \
+    _Pragma("clang diagnostic pop")     \
+    TINT_REQUIRE_SEMICOLON
+
 #define TINT_BEGIN_DISABLE_WARNING(name)     \
     _Pragma("clang diagnostic push")         \
     TINT_CONCAT(TINT_DISABLE_WARNING_, name) \
@@ -135,6 +152,11 @@
 
 #define TINT_UNLIKELY(x) __builtin_expect(!!(x), false)
 #define TINT_LIKELY(x) __builtin_expect(!!(x), true)
+
+#if __has_feature(address_sanitizer)
+#define TINT_ASAN_ENABLED
+#endif
+
 #elif defined(__GNUC__)
 ////////////////////////////////////////////////////////////////////////////////
 // GCC
@@ -163,6 +185,12 @@
     _Pragma("GCC diagnostic push") TINT_DISABLE_WARNING_UNUSED_PARAMETER TINT_REQUIRE_SEMICOLON
 #define TINT_END_DISABLE_PROTOBUF_WARNINGS() _Pragma("GCC diagnostic pop") TINT_REQUIRE_SEMICOLON
 
+#define TINT_BEGIN_DISABLE_ALL_WARNINGS()                                      \
+    _Pragma("GCC diagnostic push") _Pragma("GCC diagnostic ignored \"-Wall\"") \
+        _Pragma("GCC diagnostic ignored \"-Wextra\"") TINT_REQUIRE_SEMICOLON
+
+#define TINT_END_DISABLE_ALL_WARNINGS() _Pragma("GCC diagnostic pop") TINT_REQUIRE_SEMICOLON
+
 // clang-format off
 #define TINT_BEGIN_DISABLE_WARNING(name)     \
     _Pragma("GCC diagnostic push")           \
@@ -175,10 +203,17 @@
 
 #define TINT_UNLIKELY(x) __builtin_expect(!!(x), false)
 #define TINT_LIKELY(x) __builtin_expect(!!(x), true)
+
+#if defined(__SANITIZE_ADDRESS__)
+#define TINT_ASAN_ENABLED
+#endif
+
 #else
 ////////////////////////////////////////////////////////////////////////////////
 // Other
 ////////////////////////////////////////////////////////////////////////////////
+#define TINT_BEGIN_DISABLE_ALL_WARNINGS() TINT_REQUIRE_SEMICOLON
+#define TINT_END_DISABLE_ALL_WARNINGS TINT_REQUIRE_SEMICOLON
 #define TINT_BEGIN_DISABLE_WARNING(name) TINT_REQUIRE_SEMICOLON
 #define TINT_END_DISABLE_WARNING(name) TINT_REQUIRE_SEMICOLON
 #define TINT_BEGIN_DISABLE_PROTOBUF_WARNINGS() TINT_REQUIRE_SEMICOLON
diff --git a/tools/src/cmd/gen/build/BUILD.bazel.tmpl b/tools/src/cmd/gen/build/BUILD.bazel.tmpl
index b2537e5..46eafcb 100644
--- a/tools/src/cmd/gen/build/BUILD.bazel.tmpl
+++ b/tools/src/cmd/gen/build/BUILD.bazel.tmpl
@@ -141,6 +141,7 @@
 */ -}}
 {{- define "ExternalDependencyTarget"}}
 {{-        if eq $.Name "abseil"             -}}"@abseil_cpp//absl/strings",
+{{-   else if eq $.Name "dxc-include"        -}}{{/* unsupported */}}
 {{-   else if eq $.Name "glslang-res-limits" -}}{{/* unsupported */}}
 {{-   else if eq $.Name "glslang"            -}}{{/* unsupported */}}
 {{-   else if eq $.Name "gmock"              -}}"@gtest",
diff --git a/tools/src/cmd/gen/build/BUILD.gn.tmpl b/tools/src/cmd/gen/build/BUILD.gn.tmpl
index 1b299eb..fee28b7 100644
--- a/tools/src/cmd/gen/build/BUILD.gn.tmpl
+++ b/tools/src/cmd/gen/build/BUILD.gn.tmpl
@@ -165,6 +165,7 @@
 */ -}}
 {{- define "ExternalDependencyTargets"}}
 {{-        if eq $.Name "abseil"             -}}"${tint_src_dir}:abseil",
+{{-   else if eq $.Name "dxc-include"        -}}"${tint_src_dir}:dxc-include",
 {{-   else if eq $.Name "glslang-res-limits" -}}"${tint_glslang_dir}:glslang_default_resource_limits_sources",
 {{-   else if eq $.Name "glslang"            -}}"${tint_glslang_dir}:glslang_lib_sources",
 {{-   else if eq $.Name "google-benchmark"   -}}"${tint_src_dir}:google_benchmark",
diff --git a/tools/src/cmd/tests/main.go b/tools/src/cmd/tests/main.go
index 890f8c4..4994444 100644
--- a/tools/src/cmd/tests/main.go
+++ b/tools/src/cmd/tests/main.go
@@ -138,7 +138,7 @@
 	verbose, useIr, generateExpected, generateSkip := false, false, false, false
 	flag.StringVar(&formatList, "format", "all", "comma separated list of formats to emit. Possible values are: all, wgsl, spvasm, msl, hlsl, hlsl-dxc, hlsl-fxc, glsl")
 	flag.StringVar(&ignore, "ignore", "**.expected.*", "files to ignore in globs")
-	flag.StringVar(&dxcPath, "dxc", "", "path to DXC executable for validating HLSL output")
+	flag.StringVar(&dxcPath, "dxcompiler", "", "path to DXC DLL for validating HLSL output")
 	flag.StringVar(&fxcPath, "fxc", "", "path to FXC DLL for validating HLSL output")
 	flag.StringVar(&tintPath, "tint", defaultTintPath(), "path to the tint executable")
 	flag.StringVar(&xcrunPath, "xcrun", "", "path to xcrun executable for validating MSL output")
@@ -240,6 +240,13 @@
 		defaultMSLExe = "metal.exe"
 	}
 
+	defaultDXCDll := "libdxcompiler.so"
+	if runtime.GOOS == "windows" {
+		defaultDXCDll = "dxcompiler.dll"
+	} else if runtime.GOOS == "darwin" {
+		defaultDXCDll = "libdxcompiler.dylib"
+	}
+
 	toolchainHash := sha256.New()
 
 	// If explicit verification compilers have been specified, check they exist.
@@ -249,7 +256,7 @@
 		lang string
 		path *string
 	}{
-		{"dxc", "hlsl-dxc", &dxcPath},
+		{defaultDXCDll, "hlsl-dxc", &dxcPath},
 		{"d3dcompiler_47.dll", "hlsl-fxc", &fxcPath},
 		{defaultMSLExe, "msl", &xcrunPath},
 	} {