[Clang][HLSL][DebugInfo] Emit dx.source metadata nodes (#199689)

Add HLSL debug metadata emission for DXIL by embedding non-system source
files (including included headers) into dx.source.contents, recording
the main file name and user defines, and serializing the dxc-style
command line into dx.source.args.

These metadata nodes are needed for emitting SRCI part of a debug
DXContainer in llc.

Introduce -fdx-record-command-line option to cc1 to carry the escaped
dxc driver command line into cc1.
Introduce -fdx-no-source-metadata CodeGen option to cc1, to be able to
disable dx.source metadata nodes emission. This comes in handy for
writing debug info tests, so that CHECK-NOT lines do not match
themselves in LLVM IR. For example, without this option, in
`clang/test/CodeGenHLSL/debug/source-language.hlsl`, CHECK-V4-NOT line
gets a match on the line with dx.source.contents node.
This commit is contained in:
Vladislav Dzhidzhoev 2026-06-15 13:40:32 +02:00 committed by GitHub
parent 8717831a8c
commit 63c9264c77
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 433 additions and 7 deletions

View file

@ -521,6 +521,9 @@ CODEGENOPT(ResMayAlias, 1, 0, Benign)
/// Assume that all resources are bound if enabled
CODEGENOPT(AllResourcesBound, 1, 0, Benign)
/// Do not embed dx.source.* metadata in HLSL modules.
CODEGENOPT(DisableDXSourceMetadata, 1, 0, Benign)
/// Controls the x64 Windows unwind info version.
ENUM_CODEGENOPT(WinX64EHUnwind, WinX64EHUnwindMode, 3,
WinX64EHUnwindMode::Default, Benign)

View file

@ -268,6 +268,14 @@ public:
/// if non-empty.
std::string RecordCommandLine;
/// The string containing the commandline for the dx.source.args metadata,
/// if non-empty.
std::string HLSLRecordCommandLine;
/// The vector contains parsed commandline for the dx.source.args metadata,
/// if parsing was successful.
llvm::SmallVector<llvm::SmallString<8>> HLSLParsedCommandLine;
llvm::SmallVector<std::pair<std::string, std::string>, 0> DebugPrefixMap;
/// Prefix replacement map for source-based code coverage to remap source

View file

@ -352,6 +352,8 @@ def err_drv_too_many_actions: Error<
"only one action option is allowed. Got %0">;
def err_drv_invalid_value : Error<"invalid value '%1' in '%0'">;
def err_drv_invalid_int_value : Error<"invalid integral value '%1' in '%0'">;
def err_drv_invalid_escaped_command_line
: Error<"invalid escaped command line: %0">;
def err_drv_invalid_value_with_suggestion : Error<
"invalid value '%1' in '%0', expected one of: %2">;
def err_drv_alignment_not_power_of_two : Error<"alignment is not a power of 2 in '%0'">;

View file

@ -458,6 +458,9 @@ def warn_hlsl_langstd_minimal :
Warning<"support for HLSL language version %0 is incomplete, "
"recommend using %1 instead">,
InGroup<HLSLDXCCompat>;
def warn_hlsl_failed_to_embed_source
: Warning<"failed to embed source for '%0' into dx.source.contents">,
InGroup<HLSLDXCCompat>;
def err_data_layout_mismatch : Error<"backend data layout '%0' does not match "
"expected target description '%1'">;

View file

@ -280,8 +280,8 @@ const char *renderEscapedCommandLine(const ToolChain &TC,
/// line options that were passed.
bool shouldRecordCommandLine(const ToolChain &TC,
const llvm::opt::ArgList &Args,
bool &FRecordCommandLine,
bool &GRecordCommandLine);
bool &FRecordCommandLine, bool &GRecordCommandLine,
bool &DXRecordCommandLine);
void renderGlobalISelOptions(const Driver &D, const llvm::opt::ArgList &Args,
llvm::opt::ArgStringList &CmdArgs,

View file

@ -77,6 +77,10 @@ std::string GetResourcesPath(StringRef BinaryPath);
/// executable), for finding the builtin compiler path.
std::string GetResourcesPath(const char *Argv0, void *MainAddr);
/// Parse a space-separated command line with escaped spaces and backslashes.
llvm::Expected<llvm::SmallVector<llvm::SmallString<8>>>
parseEscapedCommandLine(const char *CommandLine);
} // namespace clang
#endif // LLVM_CLANG_OPTIONS_OPTIONUTILS_H

View file

@ -9723,6 +9723,19 @@ def dxc_rootsig_define :
Alias<fdx_rootsignature_define>,
Group<dxc_Group>,
Visibility<[DXCOption]>;
def dxc_record_command_line
: Separate<["-"], "fdx-record-command-line">,
Group<dxc_Group>,
Visibility<[CC1Option]>,
MarshallingInfoString<CodeGenOpts<"HLSLRecordCommandLine">>,
HelpText<
"Command line arguments to embed in the dx.source.args metadata">;
def dxc_no_source_metadata
: Flag<["-"], "fdx-no-source-metadata">,
Group<dxc_Group>,
Visibility<[CC1Option]>,
MarshallingInfoFlag<CodeGenOpts<"DisableDXSourceMetadata">>,
HelpText<"Do not embed source info metadata in HLSL modules">;
def hlsl_entrypoint : Option<["-"], "hlsl-entry", KIND_SEPARATE>,
Group<dxc_Group>,
Visibility<[ClangOption, CC1Option]>,

View file

@ -26,9 +26,12 @@
#include "clang/AST/HLSLResource.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/AST/Type.h"
#include "clang/Basic/DiagnosticDriver.h"
#include "clang/Basic/DiagnosticFrontend.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetOptions.h"
#include "llvm/ADT/DenseMap.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/SmallVector.h"
@ -45,6 +48,7 @@
#include "llvm/Support/Alignment.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FormatVariadic.h"
#include "llvm/Support/Path.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"
#include <cstdint>
#include <optional>
@ -190,6 +194,100 @@ findAssociatedResourceDeclForStruct(ASTContext &AST, const MemberExpr *ME) {
return nullptr;
}
void addSourceInfo(CodeGenModule &CGM, llvm::Module &M) {
auto &SM = CGM.getContext().getSourceManager();
auto &Macros = CGM.getPreprocessorOpts().Macros;
auto &CodeGenOpts = CGM.getCodeGenOpts();
auto &Ctx = M.getContext();
// Names and content of shader source code files.
llvm::NamedMDNode *DXContents =
M.getOrInsertNamedMetadata("dx.source.contents");
auto addFile = [&](const std::pair<StringRef, StringRef> &NameContent) {
llvm::MDTuple *FileInfo =
llvm::MDNode::get(Ctx, {llvm::MDString::get(Ctx, NameContent.first),
llvm::MDString::get(Ctx, NameContent.second)});
DXContents->addOperand(FileInfo);
};
bool Invalid = false;
const SrcMgr::SLocEntry *MainLocEntry =
&SM.getSLocEntry(SM.getMainFileID(), &Invalid);
assert(!Invalid && "Main file SLocEntry must not be invalid!");
const SrcMgr::ContentCache &MainCCEntry =
MainLocEntry->getFile().getContentCache();
SmallVector<std::pair<std::string, StringRef>> Files;
std::optional<SmallString<256>> MainFileName;
Files.reserve(SM.local_sloc_entry_size());
for (unsigned I : llvm::seq(SM.local_sloc_entry_size())) {
const SrcMgr::SLocEntry &LocEntry = SM.getLocalSLocEntry(I);
if (!LocEntry.isFile())
continue;
const SrcMgr::FileInfo &FInfo = LocEntry.getFile();
if (isSystem(FInfo.getFileCharacteristic()))
continue;
const SrcMgr::ContentCache &CCEntry = FInfo.getContentCache();
OptionalFileEntryRef FEntry = CCEntry.OrigEntry;
if (!FEntry)
continue;
llvm::SmallString<256> Path = FEntry->getName();
llvm::sys::path::native(Path);
std::optional<llvm::MemoryBufferRef> Buffer = CCEntry.getBufferOrNone(
SM.getDiagnostics(), SM.getFileManager(), SourceLocation());
if (!Buffer) {
SM.getDiagnostics().Report(diag::warn_hlsl_failed_to_embed_source)
<< Path;
continue;
}
if (&MainCCEntry != &CCEntry) {
Files.emplace_back(Path, Buffer->getBuffer());
} else {
// Main file should be at first position.
addFile(std::make_pair(Path, Buffer->getBuffer()));
MainFileName.emplace(Path);
}
}
assert(MainFileName && "Main file not found.");
// Files other that main one should be sorted by name.
llvm::sort(Files);
#ifndef NDEBUG
for (unsigned I = 1; I < Files.size(); ++I)
assert((Files[I - 1].first != Files[I].first) &&
"duplicate files in dx.source.contents");
#endif
llvm::for_each(Files, addFile);
SmallVector<llvm::Metadata *> Defines;
Defines.reserve(Macros.size());
for (const auto &Macro : Macros) {
// Ignore undefs.
if (!Macro.second)
Defines.emplace_back(llvm::MDString::get(Ctx, Macro.first));
}
M.getOrInsertNamedMetadata("dx.source.defines")
->addOperand(llvm::MDNode::get(Ctx, Defines));
if (!CodeGenOpts.MainFileName.empty())
llvm::sys::path::native(CodeGenOpts.MainFileName, *MainFileName);
M.getOrInsertNamedMetadata("dx.source.mainFileName")
->addOperand(
llvm::MDNode::get(Ctx, llvm::MDString::get(Ctx, *MainFileName)));
SmallVector<llvm::Metadata *> Args;
Args.reserve(CodeGenOpts.HLSLParsedCommandLine.size());
if (!CodeGenOpts.HLSLParsedCommandLine.empty())
for (const auto &Arg : llvm::drop_begin(CodeGenOpts.HLSLParsedCommandLine))
Args.push_back(llvm::MDString::get(Ctx, Arg));
M.getOrInsertNamedMetadata("dx.source.args")
->addOperand(llvm::MDNode::get(Ctx, Args));
}
// Find array variable declaration from DeclRef expression
static const ValueDecl *getArrayDecl(ASTContext &AST, const Expr *E) {
E = E->IgnoreImpCasts();
@ -749,6 +847,10 @@ void CGHLSLRuntime::finishCodeGen() {
Triple T(M.getTargetTriple());
if (T.getArch() == Triple::ArchType::dxil)
addDxilValVersion(TargetOpts.DxilValidatorVersion, M);
if (!CodeGenOpts.DisableDXSourceMetadata &&
CodeGenOpts.getDebugInfo() >=
llvm::codegenoptions::DebugInfoKind::DebugInfoConstructor)
addSourceInfo(CGM, M);
if (CodeGenOpts.ResMayAlias)
M.setModuleFlag(llvm::Module::ModFlagBehavior::Error, "dx.resmayalias", 1);
if (CodeGenOpts.AllResourcesBound)

View file

@ -8091,7 +8091,9 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
// By default, -gno-record-gcc-switches is set on and no recording.
auto GRecordSwitches = false;
auto FRecordSwitches = false;
if (shouldRecordCommandLine(TC, Args, FRecordSwitches, GRecordSwitches)) {
bool DXRecordSwitches = false;
if (shouldRecordCommandLine(TC, Args, FRecordSwitches, GRecordSwitches,
DXRecordSwitches)) {
auto FlagsArgString = renderEscapedCommandLine(TC, Args);
if (TC.UseDwarfDebugFlags() || GRecordSwitches) {
CmdArgs.push_back("-dwarf-debug-flags");
@ -8101,6 +8103,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
CmdArgs.push_back("-record-command-line");
CmdArgs.push_back(FlagsArgString);
}
if (DXRecordSwitches) {
CmdArgs.push_back("-fdx-record-command-line");
CmdArgs.push_back(FlagsArgString);
}
}
// Host-side offloading compilation receives all device-side outputs. Include

View file

@ -3350,7 +3350,8 @@ const char *tools::renderEscapedCommandLine(const ToolChain &TC,
bool tools::shouldRecordCommandLine(const ToolChain &TC,
const llvm::opt::ArgList &Args,
bool &FRecordCommandLine,
bool &GRecordCommandLine) {
bool &GRecordCommandLine,
bool &DXRecordCommandLine) {
const Driver &D = TC.getDriver();
const llvm::Triple &Triple = TC.getEffectiveTriple();
const std::string &TripleStr = Triple.getTriple();
@ -3361,13 +3362,15 @@ bool tools::shouldRecordCommandLine(const ToolChain &TC,
GRecordCommandLine =
Args.hasFlag(options::OPT_grecord_command_line,
options::OPT_gno_record_command_line, false);
DXRecordCommandLine = Triple.isDXIL() && Args.hasArg(options::OPT_g_Flag);
if (FRecordCommandLine && !Triple.isOSBinFormatELF() &&
!Triple.isOSBinFormatXCOFF() && !Triple.isOSBinFormatMachO())
D.Diag(diag::err_drv_unsupported_opt_for_target)
<< Args.getLastArg(options::OPT_frecord_command_line)->getAsString(Args)
<< TripleStr;
return FRecordCommandLine || TC.UseDwarfDebugFlags() || GRecordCommandLine;
return FRecordCommandLine || TC.UseDwarfDebugFlags() || GRecordCommandLine ||
DXRecordCommandLine;
}
void tools::renderGlobalISelOptions(const Driver &D, const ArgList &Args,

View file

@ -1299,7 +1299,9 @@ void Flang::ConstructJob(Compilation &C, const JobAction &JA,
bool FRecordCmdLine = false;
bool GRecordCmdLine = false;
if (shouldRecordCommandLine(TC, Args, FRecordCmdLine, GRecordCmdLine)) {
bool DXRecordCmdLine = false;
if (shouldRecordCommandLine(TC, Args, FRecordCmdLine, GRecordCmdLine,
DXRecordCmdLine)) {
const char *CmdLine = renderEscapedCommandLine(TC, Args);
if (FRecordCmdLine) {
CmdArgs.push_back("-record-command-line");

View file

@ -2327,6 +2327,16 @@ bool CompilerInvocation::ParseCodeGenArgs(CodeGenOptions &Opts, ArgList &Args,
Opts.StaticClosure = Args.hasArg(options::OPT_static_libclosure);
if (!Opts.HLSLRecordCommandLine.empty()) {
auto ParsedArgs =
clang::parseEscapedCommandLine(Opts.HLSLRecordCommandLine.c_str());
if (!ParsedArgs)
Diags.Report(diag::err_drv_invalid_escaped_command_line)
<< llvm::toString(ParsedArgs.takeError());
else
Opts.HLSLParsedCommandLine = std::move(*ParsedArgs);
}
return Diags.getNumErrors() == NumErrorsBefore;
}

View file

@ -244,3 +244,35 @@ std::string clang::GetResourcesPath(const char *Argv0, void *MainAddr) {
llvm::sys::fs::getMainExecutable(Argv0, MainAddr);
return GetResourcesPath(ClangExecutable);
}
static bool isSpaceOrNull(char c) { return !c || c == ' '; }
static Expected<const char *> unescapeUntilSpace(const char *Arg,
SmallVectorImpl<char> &Res) {
for (; !isSpaceOrNull(*Arg); ++Arg) {
if (*Arg == '\\') {
++Arg;
if (*Arg != '\\' && *Arg != ' ')
return llvm::createStringError(
llvm::inconvertibleErrorCode(),
"only escaped backslashes and spaces are supported");
}
Res.push_back(*Arg);
}
return Arg;
}
Expected<SmallVector<SmallString<8>>>
clang::parseEscapedCommandLine(const char *CommandLine) {
SmallVector<SmallString<8>> Res;
while (*CommandLine) {
Expected<const char *> ArgEnd =
unescapeUntilSpace(CommandLine, Res.emplace_back());
if (!ArgEnd)
return ArgEnd.takeError();
CommandLine = *ArgEnd;
if (*CommandLine == ' ')
++CommandLine;
}
return Res;
}

View file

@ -0,0 +1,10 @@
#ifndef GUARD
#define GUARD
#include "b.hlsl"
float helper1_add(float a, float b) {
return a + b;
}
#endif

View file

@ -0,0 +1,3 @@
float helper2_mul(float a, float b) {
return a * b;
}

View file

@ -0,0 +1,3 @@
float sys_helper(float a, float b) {
return a * b;
}

View file

@ -5,24 +5,28 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -x hlsl -emit-llvm \
// RUN: -disable-llvm-passes -hlsl-entry main \
// RUN: -debug-info-kind=standalone -dwarf-version=4 -o - %s \
// RUN: -fdx-no-source-metadata \
// RUN: | FileCheck %s --check-prefix=CHECK-V4
// SPIR-V target, DWARFv4
// RUN: %clang_cc1 -triple spirv-unknown-vulkan-compute -x hlsl -emit-llvm \
// RUN: -disable-llvm-passes -hlsl-entry main \
// RUN: -debug-info-kind=standalone -dwarf-version=4 -o - %s \
// RUN: -fdx-no-source-metadata \
// RUN: | FileCheck %s --check-prefix=CHECK-V4
// DXIL target, DWARFv6
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-compute -x hlsl -emit-llvm \
// RUN: -disable-llvm-passes -hlsl-entry main \
// RUN: -debug-info-kind=standalone -dwarf-version=6 -o - %s \
// RUN: -fdx-no-source-metadata \
// RUN: | FileCheck %s --check-prefix=CHECK-V6
// SPIR-V target, DWARFv6
// RUN: %clang_cc1 -triple spirv-unknown-vulkan-compute -x hlsl -emit-llvm \
// RUN: -disable-llvm-passes -hlsl-entry main \
// RUN: -debug-info-kind=standalone -dwarf-version=6 -o - %s \
// RUN: -fdx-no-source-metadata \
// RUN: | FileCheck %s --check-prefix=CHECK-V6
// CHECK-V4: !DICompileUnit(language: DW_LANG_HLSL,

View file

@ -0,0 +1,12 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm \
// RUN: -debug-info-kind=constructor -fdx-no-source-metadata -o - \
// RUN: -disable-llvm-passes %s | FileCheck %s
// CHECK-NOT: !dx.source.contents
// CHECK-NOT: !dx.source.defines
// CHECK-NOT: !dx.source.mainFileName
// CHECK-NOT: !dx.source.args
float foo(float a, float b) {
return a + b;
}

View file

@ -0,0 +1,21 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm \
// RUN: -debug-info-kind=constructor -I %S/Inputs -isystem %S/SysInputs \
// RUN: -o - %s | FileCheck %s
#include "a.hlsl"
#include "a.hlsl"
#include <c.hlsl>
// The main file appears first in dx.source.contents; included files are
// appended in sorted order afterwards. Duplicate and system includes are ignored.
// CHECK: !dx.source.contents = !{![[MAIN:[0-9]+]], ![[H1:[0-9]+]], ![[H2:[0-9]+]]}
// CHECK: !dx.source.mainFileName = !{![[MAIN_FILE_NAME:[0-9]+]]}
// CHECK: ![[MAIN]] = !{!"{{.*[\\/]dx-source-metadata-includes.hlsl}}", !"{{.*}}"}
// CHECK: ![[H1]] = !{!"{{.*[\\/]a.hlsl}}", !"{{.*}}"}
// CHECK: ![[H2]] = !{!"{{.*[\\/]b.hlsl}}", !"{{.*}}"}
// CHECK: ![[MAIN_FILE_NAME]] = !{!"{{.*[\\/]dx-source-metadata-includes.hlsl}}"}
float foo(float a, float b) {
return helper1_add(a, b) + helper2_mul(a, b) + sys_helper(a, b);
}

View file

@ -0,0 +1,16 @@
// RUN: not %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm \
// RUN: -debug-info-kind=constructor \
// RUN: -fdx-record-command-line "clang_dxc \\" \
// RUN: -o - %s 2>&1 | FileCheck %s
// RUN: not %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm \
// RUN: -debug-info-kind=constructor \
// RUN: -fdx-record-command-line "clang_dxc \\" \
// RUN: -fsyntax-only \
// RUN: -o - %s 2>&1 | FileCheck %s
// CHECK: error: invalid escaped command line: only escaped backslashes and spaces are supported
float foo(float a, float b) {
return a + b;
}

View file

@ -0,0 +1,28 @@
// RUN: %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm \
// RUN: -debug-info-kind=constructor -DUSER_DEF0=42 -DUSER_DEF1=43 -UUSER_DEF1 \
// RUN: -fdx-record-command-line "clang_dxc -g -Tlib_6_3 -DUSER_DEF0=42 -DUSER_DEF1=43 C:\\\\dx-source-metadata.hlsl" \
// RUN: -o - %s | FileCheck %s
// RUN: cat %s | %clang_cc1 -triple dxil-pc-shadermodel6.3-library -x hlsl -emit-llvm \
// RUN: -debug-info-kind=constructor \
// RUN: -fdx-record-command-line "clang_dxc -g -Tlib_6_3 C:\\\\dx-source-metadata.hlsl" \
// RUN: -o - | FileCheck %s --check-prefix=CHECK-FROM-PIPE
// CHECK: !dx.source.contents = !{![[CONTENTS:[0-9]+]]}
// CHECK: !dx.source.defines = !{![[DEFINES:[0-9]+]]}
// CHECK: !dx.source.mainFileName = !{![[MAIN:[0-9]+]]}
// CHECK: !dx.source.args = !{![[ARGS:[0-9]+]]}
// CHECK: ![[CONTENTS]] = !{!"{{.*[\\/]dx-source-metadata.hlsl}}", !"{{.*}}"}
// CHECK: ![[DEFINES]] = !{!"USER_DEF0=42", !"USER_DEF1=43"}
// CHECK: ![[MAIN]] = !{!"{{.*[\\/]dx-source-metadata.hlsl}}"}
// CHECK: ![[ARGS]] = !{!"-g", !"-Tlib_6_3", !"-DUSER_DEF0=42", !"-DUSER_DEF1=43", !"C:\\dx-source-metadata.hlsl"}
// CHECK-FROM-PIPE: !dx.source.contents = !{![[CONTENTS:[0-9]+]]}
// CHECK-FROM-PIPE: !dx.source.mainFileName = !{![[MAIN:[0-9]+]]}
// CHECK-FROM-PIPE: ![[CONTENTS]] = !{!"<stdin>", !"{{.*}}"}
// CHECK-FROM-PIPE: ![[MAIN]] = !{!"<stdin>"}
float foo(float a, float b) {
return a + b;
}

View file

@ -1 +1,2 @@
config.suffixes = [".c", ".hlsl"]
config.excludes.add("SysInputs")

View file

@ -1,4 +1,4 @@
// RUN: %clang_dxc -Tlib_6_7 -### -g %s 2>&1 | FileCheck %s
// RUN: %clang_dxc -Tlib_6_7 -### -g %s 2>&1 | FileCheck %s --check-prefix=CHECK,CHECK-CMD
// RUN: %clang_dxc -Tlib_6_7 -### /Zi %s 2>&1 | FileCheck %s
// RUN: %clang_dxc -Tlib_6_7 -### /Zi /Qembed_debug %s 2>&1 | FileCheck %s
// RUN: %clang_dxc -Tlib_6_7 -### -Zi %s 2>&1 | FileCheck %s
@ -13,3 +13,6 @@
// CHECK-SAME: "-debug-info-kind=constructor"
// Make sure dwarf-version is 4.
// CHECK-DWARF-SAME: -dwarf-version=4
// Make sure dxc command line arguments are passed to clang invocation.
// CHECK-SAME: -fdx-record-command-line
// CHECK-CMD-SAME: --driver-mode=dxc -T lib_6_7 -### -g {{.*}}dxc_debug.hlsl

View file

@ -1,6 +1,7 @@
add_clang_unittest(ClangDriverTests
DistroTest.cpp
DXCModeTest.cpp
EscapedCommandLineTest.cpp
GCCVersionTest.cpp
ToolChainTest.cpp
ModuleCacheTest.cpp
@ -11,6 +12,7 @@ add_clang_unittest(ClangDriverTests
clangDriver
clangBasic
clangFrontend # For TextDiagnosticPrinter.
clangOptions
clangSerialization
LLVM_COMPONENTS
${LLVM_TARGETS_TO_BUILD}

View file

@ -0,0 +1,135 @@
//===- unittests/Driver/EscapedCommandLineTest.cpp ------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// Unit tests for -record-command-line.
//
//===----------------------------------------------------------------------===//
#include "clang/Driver/CommonArgs.h"
#include "clang/Options/OptionUtils.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/ADT/iterator_range.h"
#include "gtest/gtest.h"
using namespace clang::driver::tools;
using namespace llvm;
using ArgStr = SmallString<8>;
using ArgVec = SmallVector<ArgStr>;
static ArgStr escape(const char *Arg) {
ArgStr Res;
escapeSpacesAndBackslashes(Arg, Res);
return Res;
}
TEST(EscapedCommandLineTest, EscapeEmpty) { EXPECT_EQ(escape(""), ""); }
TEST(EscapedCommandLineTest, EscapeNoSpecialChars) {
EXPECT_EQ(escape("hello"), "hello");
EXPECT_EQ(escape("-Tlib_6_3"), "-Tlib_6_3");
}
TEST(EscapedCommandLineTest, EscapeSpace) {
EXPECT_EQ(escape("foo bar"), "foo\\ bar");
EXPECT_EQ(escape(" leading"), "\\ leading");
EXPECT_EQ(escape("trailing "), "trailing\\ ");
}
TEST(EscapedCommandLineTest, EscapeBackslash) {
EXPECT_EQ(escape("a\\b"), "a\\\\b");
}
TEST(EscapedCommandLineTest, EscapeSpaceAndBackslash) {
EXPECT_EQ(escape("a\\ b"), "a\\\\\\ b");
}
static ArgVec parse(const char *CommandLine) {
ArgVec Res;
auto ParsedArgs = clang::parseEscapedCommandLine(CommandLine);
if (!ParsedArgs) {
ADD_FAILURE() << llvm::toString(ParsedArgs.takeError());
return Res;
}
for (const auto &Arg : *ParsedArgs)
Res.emplace_back(Arg.begin(), Arg.end());
return Res;
}
TEST(EscapedCommandLineTest, ParseEmpty) { EXPECT_TRUE(parse("").empty()); }
TEST(EscapedCommandLineTest, ParseSingleArg) {
EXPECT_EQ(parse("hello"), ArgVec({StringRef("hello")}));
}
TEST(EscapedCommandLineTest, ParseMultipleArgs) {
auto Args = parse("clang -Tlib_6_3 foo.hlsl");
ASSERT_EQ(Args.size(), 3u);
EXPECT_EQ(Args[0], "clang");
EXPECT_EQ(Args[1], "-Tlib_6_3");
EXPECT_EQ(Args[2], "foo.hlsl");
}
TEST(EscapedCommandLineTest, ParseEscapedSpace) {
auto Args = parse("foo\\ bar baz");
ASSERT_EQ(Args.size(), 2u);
EXPECT_EQ(Args[0], "foo bar");
EXPECT_EQ(Args[1], "baz");
}
TEST(EscapedCommandLineTest, ParseEscapedBackslash) {
auto Args = parse("a\\\\b");
ASSERT_EQ(Args.size(), 1u);
EXPECT_EQ(Args[0], "a\\b");
}
TEST(EscapedCommandLineTest, ParseInvalidEscape) {
auto Args = clang::parseEscapedCommandLine("clang \\");
ASSERT_FALSE(Args);
EXPECT_EQ(llvm::toString(Args.takeError()),
"only escaped backslashes and spaces are supported");
}
static ArgVec roundTrip(ArgVec Args) {
SmallString<256> Joined;
escapeSpacesAndBackslashes(Args.begin()->c_str(), Joined);
for (auto &Arg : llvm::make_range(Args.begin() + 1, Args.end())) {
Joined += " ";
escapeSpacesAndBackslashes(Arg.c_str(), Joined);
}
return parse(Joined.c_str());
}
TEST(EscapedCommandLineTest, RoundTripSimple) {
ArgVec Args;
Args.emplace_back("clang");
Args.emplace_back("-O2");
Args.emplace_back("foo.cpp");
EXPECT_EQ(roundTrip(Args), Args);
}
TEST(EscapedCommandLineTest, RoundTripArgWithSpace) {
ArgVec Args;
Args.emplace_back("clang");
Args.emplace_back("path with spaces/file.cpp");
EXPECT_EQ(roundTrip(Args), Args);
}
TEST(EscapedCommandLineTest, RoundTripArgWithBackslash) {
ArgVec Args;
Args.emplace_back("clang");
Args.emplace_back("C:\\path\\file.cpp");
EXPECT_EQ(roundTrip(Args), Args);
}
TEST(EscapedCommandLineTest, RoundTripArgWithSpaceAndBackslash) {
ArgVec Args;
Args.emplace_back("clang");
Args.emplace_back("C:\\path with space\\file.cpp");
EXPECT_EQ(roundTrip(Args), Args);
}