From 0b686f347b439979f89b566772b662fc2f02d3e0 Mon Sep 17 00:00:00 2001 From: Tex Riddell Date: Sun, 14 Mar 2021 22:13:26 -0700 Subject: [PATCH] FileChecker improvements: VFS, %dxl, FileCheck -D, error reporting (#3576) - VFS captures output files for duration of test, enabling: - %dxl test IDxcLinker - add -D to FileCheck args to supply defined variables - report failing RUN command when not consumed by FileCheck or XFail --- include/dxc/Test/DxcTestUtils.h | 5 + .../unittests/HLSLTestLib/FileCheckerTest.cpp | 228 +++++++++++++++++- 2 files changed, 225 insertions(+), 8 deletions(-) diff --git a/include/dxc/Test/DxcTestUtils.h b/include/dxc/Test/DxcTestUtils.h index d7be98b09..91a3a8816 100644 --- a/include/dxc/Test/DxcTestUtils.h +++ b/include/dxc/Test/DxcTestUtils.h @@ -60,6 +60,9 @@ public: int Run(); }; +// wstring because most uses need UTF-16: IDxcResult output names, include handler +typedef std::map> FileMap; + // The result of running a single command in a run pipeline struct FileRunCommandResult { CComPtr OpResult; // The operation result, if any. @@ -109,6 +112,7 @@ public: std::string Command; // Command to run, eg %dxc std::string Arguments; // Arguments to command LPCWSTR CommandFileName; // File name replacement for %s + FileMap *pVFS = nullptr; // Files in virtual file system private: FileRunCommandResult RunFileChecker(const FileRunCommandResult *Prior, LPCWSTR dumpName = nullptr); @@ -117,6 +121,7 @@ private: FileRunCommandResult RunOpt(dxc::DxcDllSupport &DllSupport, const FileRunCommandResult *Prior); FileRunCommandResult RunD3DReflect(dxc::DxcDllSupport &DllSupport, const FileRunCommandResult *Prior); FileRunCommandResult RunDxr(dxc::DxcDllSupport &DllSupport, const FileRunCommandResult *Prior); + FileRunCommandResult RunLink(dxc::DxcDllSupport &DllSupport, const FileRunCommandResult *Prior); FileRunCommandResult RunTee(const FileRunCommandResult *Prior); FileRunCommandResult RunXFail(const FileRunCommandResult *Prior); FileRunCommandResult RunDxilVer(dxc::DxcDllSupport& DllSupport, const FileRunCommandResult* Prior); diff --git a/tools/clang/unittests/HLSLTestLib/FileCheckerTest.cpp b/tools/clang/unittests/HLSLTestLib/FileCheckerTest.cpp index 968a4aa90..87209490f 100644 --- a/tools/clang/unittests/HLSLTestLib/FileCheckerTest.cpp +++ b/tools/clang/unittests/HLSLTestLib/FileCheckerTest.cpp @@ -39,6 +39,7 @@ #include "dxc/dxctools.h" #include "dxc/Support/HLSLOptions.h" #include "dxc/Support/Unicode.h" +#include "dxc/Support/microcom.h" #include "dxc/DxilContainer/DxilContainer.h" #include "dxc/Test/D3DReflectionDumper.h" @@ -107,6 +108,9 @@ FileRunCommandResult FileRunCommandPart::Run(dxc::DxcDllSupport &DllSupport, con else if (0 == _stricmp(Command.c_str(), "%dxr")) { return RunDxr(DllSupport, Prior); } + else if (0 == _stricmp(Command.c_str(), "%dxl")) { + return RunLink(DllSupport, Prior); + } else if (pPluginToolsPaths != nullptr) { auto it = pPluginToolsPaths->find(Command.c_str()); if (it != pPluginToolsPaths->end()) { @@ -248,6 +252,85 @@ static HRESULT GetDxilBitcode(dxc::DxcDllSupport &DllSupport, IDxcBlob *pCompile return S_OK; } +// Simple virtual file system include handler for test, fall back to default include handler +class IncludeHandlerVFSOverlayForTest : public IDxcIncludeHandler { +private: + DXC_MICROCOM_TM_REF_FIELDS() + +public: + DXC_MICROCOM_TM_ADDREF_RELEASE_IMPL() + DXC_MICROCOM_TM_CTOR(IncludeHandlerVFSOverlayForTest) + + HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void **ppvObject) override { + return DoBasicQueryInterface(this, iid, ppvObject); + } + + const FileMap *pVFS = nullptr; + CComPtr pInnerIncludeHandler; + + HRESULT STDMETHODCALLTYPE LoadSource( + _In_ LPCWSTR pFilename, // Candidate filename. + _COM_Outptr_result_maybenull_ IDxcBlob **ppIncludeSource // Resultant source object for included file, nullptr if not found. + ) override { + if (!ppIncludeSource) + return E_INVALIDARG; + *ppIncludeSource = nullptr; + if (!pFilename) + return E_INVALIDARG; + try { + if (pVFS) { + auto it = pVFS->find(pFilename); + if (it != pVFS->end()) { + return it->second.QueryInterface(ppIncludeSource); + } + } + if (pInnerIncludeHandler) { + return pInnerIncludeHandler->LoadSource(pFilename, ppIncludeSource); + } + return E_FAIL; + } + CATCH_CPP_RETURN_HRESULT(); + } +}; + +static IncludeHandlerVFSOverlayForTest *AllocVFSIncludeHandler(IUnknown *pUnkLibrary, const FileMap *pVFS) { + CComPtr pVFSIncludeHandler = IncludeHandlerVFSOverlayForTest::Alloc(DxcGetThreadMallocNoRef()); + IFTBOOL(pVFSIncludeHandler, E_OUTOFMEMORY); + if (pUnkLibrary) { + CComPtr pInnerIncludeHandler; + CComPtr pUtils; + if (SUCCEEDED(pUnkLibrary->QueryInterface(IID_PPV_ARGS(&pUtils)))) { + IFT(pUtils->CreateDefaultIncludeHandler(&pInnerIncludeHandler)); + } else { + CComPtr pLibrary; + if (SUCCEEDED(pUnkLibrary->QueryInterface(IID_PPV_ARGS(&pLibrary)))) { + IFT(pLibrary->CreateIncludeHandler(&pInnerIncludeHandler)); + } + } + pVFSIncludeHandler->pInnerIncludeHandler = pInnerIncludeHandler; + } + pVFSIncludeHandler->pVFS = pVFS; + return pVFSIncludeHandler.Detach(); +} + +static void AddOutputsToFileMap(IUnknown *pUnkResult, FileMap *pVFS) { + // If there is IDxcResult, save named output blobs to Files. + if (pUnkResult && pVFS) { + CComPtr pResult; + if (SUCCEEDED(pUnkResult->QueryInterface(IID_PPV_ARGS(&pResult)))) { + for (unsigned i = 0; i < pResult->GetNumOutputs(); i++) { + CComPtr pOutput; + CComPtr pOutputName; + if (SUCCEEDED(pResult->GetOutput(pResult->GetOutputByIndex(i), + IID_PPV_ARGS(&pOutput), &pOutputName)) && + pOutput && pOutputName && pOutputName->GetStringLength() > 0) { + (*pVFS)[pOutputName->GetStringPointer()] = pOutput; + } + } + } + } +} + static HRESULT CompileForHash(hlsl::options::DxcOpts &opts, LPCWSTR CommandFileName, dxc::DxcDllSupport &DllSupport, std::vector &flags, IDxcBlob **ppHashBlob, std::string &output) { CComPtr pLibrary; CComPtr pCompiler; @@ -466,13 +549,12 @@ FileRunCommandResult FileRunCommandPart::RunDxc(dxc::DxcDllSupport &DllSupport, CComPtr pSource; CComPtr pDisassembly; CComPtr pCompiledBlob; - CComPtr pIncludeHandler; HRESULT resultStatus; IFT(DllSupport.CreateInstance(CLSID_DxcLibrary, &pLibrary)); IFT(pLibrary->CreateBlobFromFile(CommandFileName, nullptr, &pSource)); - IFT(pLibrary->CreateIncludeHandler(&pIncludeHandler)); + CComPtr pIncludeHandler = AllocVFSIncludeHandler(pLibrary, pVFS); IFT(DllSupport.CreateInstance(CLSID_DxcCompiler, &pCompiler)); IFT(pCompiler->Compile(pSource, CommandFileName, entry.c_str(), profile.c_str(), flags.data(), flags.size(), nullptr, 0, pIncludeHandler, &pResult)); @@ -698,11 +780,10 @@ FileRunCommandResult FileRunCommandPart::RunDxr(dxc::DxcDllSupport &DllSupport, CComPtr pResult; CComPtr pSource; CComPtr pResultBlob; - CComPtr pIncludeHandler; IFT(DllSupport.CreateInstance(CLSID_DxcLibrary, &pLibrary)); IFT(pLibrary->CreateBlobFromFile(CommandFileName, nullptr, &pSource)); - IFT(pLibrary->CreateIncludeHandler(&pIncludeHandler)); + CComPtr pIncludeHandler = AllocVFSIncludeHandler(pLibrary, pVFS); IFT(DllSupport.CreateInstance(CLSID_DxcRewriter, &pRewriter)); IFT(pRewriter->RewriteWithOptions(pSource, CommandFileName, flags.data(), flags.size(), nullptr, 0, @@ -725,6 +806,107 @@ FileRunCommandResult FileRunCommandPart::RunDxr(dxc::DxcDllSupport &DllSupport, return result; } +FileRunCommandResult FileRunCommandPart::RunLink(dxc::DxcDllSupport &DllSupport, const FileRunCommandResult *Prior) { + hlsl::options::MainArgs args; + hlsl::options::DxcOpts opts; + FileRunCommandResult readOptsResult = ReadOptsForDxc(args, opts, + hlsl::options::HlslFlags::CoreOption); + if (readOptsResult.ExitCode) return readOptsResult; + + std::wstring entry = + Unicode::UTF8ToUTF16StringOrThrow(opts.EntryPoint.str().c_str()); + std::wstring profile = + Unicode::UTF8ToUTF16StringOrThrow(opts.TargetProfile.str().c_str()); + std::vector flags; + + // Skip targets that require a newer compiler or validator. + // Some features may require newer compiler/validator than indicated by the + // shader model, but these should use %dxilver explicitly. + { + unsigned RequiredDxilMajor = 1, RequiredDxilMinor = 0; + llvm::StringRef stage; + IFTBOOL(ParseTargetProfile(opts.TargetProfile, stage, RequiredDxilMajor, RequiredDxilMinor), E_INVALIDARG); + if (RequiredDxilMinor != 0xF && stage.compare("rootsig") != 0) { + // Convert stage to minimum dxil/validator version: + RequiredDxilMajor = std::max(RequiredDxilMajor, (unsigned)6) - 5; + FileRunCommandResult result = CheckDxilVer(DllSupport, RequiredDxilMajor, RequiredDxilMinor, !opts.DisableValidation); + if (result.AbortPipeline) { + return result; + } + } + } + + // For now, too many tests are sensitive to stripping the refleciton info + // from the main module, so use this flag to prevent this until tests + // can be updated. + // That is, unless the test explicitly requests -Qstrip_reflect_from_dxil or -Qstrip_reflect + if (!opts.StripReflectionFromDxil && !opts.StripReflection) { + flags.push_back(L"-Qkeep_reflect_in_dxil"); + } + + std::vector argWStrings; + CopyArgsToWStrings(opts.Args, hlsl::options::CoreOption, argWStrings); + for (const std::wstring &a : argWStrings) + flags.push_back(a.data()); + + // Parse semicolon separated list of library names. + llvm::StringRef optLibraries = opts.Args.getLastArgValue(hlsl::options::OPT_INPUT); + auto libs_utf8 = strtok(optLibraries.str().c_str(), ";"); + std::vector libs_utf16; + for (auto name : libs_utf8) + libs_utf16.emplace_back(Unicode::UTF8ToUTF16StringOrThrow(name.c_str())); + std::vector libNames; + for (auto &name : libs_utf16) + libNames.emplace_back(name.data()); + + CComPtr pLibrary; + CComPtr pLinker; + CComPtr pCompiler; + CComPtr pResult; + CComPtr pDisassembly; + CComPtr pCompiledBlob; + + HRESULT resultStatus; + + IFT(DllSupport.CreateInstance(CLSID_DxcLibrary, &pLibrary)); + CComPtr pIncludeHandler = AllocVFSIncludeHandler(pLibrary, pVFS); + IFT(DllSupport.CreateInstance(CLSID_DxcLinker, &pLinker)); + IFT(DllSupport.CreateInstance(CLSID_DxcCompiler, &pCompiler)); + + for (auto name : libNames) { + CComPtr pLibBlob; + IFT(pIncludeHandler->LoadSource(name, &pLibBlob)); + IFT(pLinker->RegisterLibrary(name, pLibBlob)); + } + + IFT(pLinker->Link(entry.c_str(), profile.c_str(), libNames.data(), + libNames.size(), flags.data(), flags.size(), &pResult)); + IFT(pResult->GetStatus(&resultStatus)); + + FileRunCommandResult result = {}; + if (SUCCEEDED(resultStatus)) { + IFT(pResult->GetResult(&pCompiledBlob)); + if (!opts.AstDump) { + IFT(pCompiler->Disassemble(pCompiledBlob, &pDisassembly)); + result.StdOut = BlobToUtf8(pDisassembly); + } else { + result.StdOut = BlobToUtf8(pCompiledBlob); + } + CComPtr pStdErr; + IFT(pResult->GetErrorBuffer(&pStdErr)); + result.StdErr = BlobToUtf8(pStdErr); + result.ExitCode = 0; + } + else { + IFT(pResult->GetErrorBuffer(&pDisassembly)); + result.StdErr = BlobToUtf8(pDisassembly); + result.ExitCode = resultStatus; + } + + result.OpResult = pResult; + return result; +} + FileRunCommandResult FileRunCommandPart::RunTee(const FileRunCommandResult *Prior) { if (Prior == nullptr) { return FileRunCommandResult::Error("tee requires a prior command"); @@ -938,6 +1120,8 @@ class FileRunTestResultImpl : public FileRunTestResult { dxc::DxcDllSupport &m_support; PluginToolsPaths *m_pPluginToolsPaths; LPCWSTR m_dumpName = nullptr; + // keep track of virtual files for duration of this test (for all RUN lines) + FileMap Files; void RunHashTestFromCommands(LPCSTR commands, LPCWSTR fileName) { std::vector parts; @@ -967,13 +1151,38 @@ class FileRunTestResultImpl : public FileRunTestResult { this->ErrorMessage = "FileCheck found no commands to run"; return; } - FileRunCommandResult result; - FileRunCommandResult* previousResult = nullptr; + FileRunCommandResult *previousResult = nullptr; + FileRunCommandPart *pPrior = nullptr; for (FileRunCommandPart & part : parts) { + int priorExitCode = result.ExitCode; + part.pVFS = &Files; result = part.Run(m_support, previousResult, m_pPluginToolsPaths, dumpName); + + // If there is IDxcResult, save named output blobs to Files. + AddOutputsToFileMap(result.OpResult, &Files); + + // When current failing stage is FileCheck, print prior command, + // as well as FileCheck command that failed, to help identify + // failing commands in longer run chains. + if (result.ExitCode && + (0 == _stricmp(part.Command.c_str(), "FileCheck") || + 0 == _stricmp(part.Command.c_str(), "%FileCheck"))) { + std::ostringstream oss; + if (pPrior) { + oss << "Prior (" << priorExitCode << "): " + << pPrior->Command << pPrior->Arguments << endl; + } + oss << "Error (" << result.ExitCode << "): " + << part.Command << part.Arguments << endl; + oss << result.StdErr; + result.StdErr = oss.str(); + } + + if (result.AbortPipeline) + break; previousResult = &result; - if (result.AbortPipeline) break; + pPrior = ∂ } this->RunResult = result.ExitCode; @@ -1001,7 +1210,10 @@ public: RunFileCheckFromCommands(cmd.c_str(), fileName, dumpName); // If any of the RUN cmd fails then skip executing remaining cmds // and report the error - if (this->RunResult != 0) break; + if (this->RunResult != 0) { + this->ErrorMessage = cmd + "\n" + this->ErrorMessage; + break; + } runIdx += 1; } }