[clang] 63c9264 - [Clang][HLSL][DebugInfo] Emit dx.source metadata nodes (#199689)
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 15 04:40:38 PDT 2026
Author: Vladislav Dzhidzhoev
Date: 2026-06-15T11:40:32Z
New Revision: 63c9264c770b4ffb7b5b3816547db45969026c3f
URL: https://github.com/llvm/llvm-project/commit/63c9264c770b4ffb7b5b3816547db45969026c3f
DIFF: https://github.com/llvm/llvm-project/commit/63c9264c770b4ffb7b5b3816547db45969026c3f.diff
LOG: [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.
Added:
clang/test/CodeGenHLSL/Inputs/a.hlsl
clang/test/CodeGenHLSL/Inputs/b.hlsl
clang/test/CodeGenHLSL/SysInputs/c.hlsl
clang/test/CodeGenHLSL/dx-source-metadata-disabled.hlsl
clang/test/CodeGenHLSL/dx-source-metadata-includes.hlsl
clang/test/CodeGenHLSL/dx-source-metadata-mailformed-command-line.hlsl
clang/test/CodeGenHLSL/dx-source-metadata.hlsl
clang/unittests/Driver/EscapedCommandLineTest.cpp
Modified:
clang/include/clang/Basic/CodeGenOptions.def
clang/include/clang/Basic/CodeGenOptions.h
clang/include/clang/Basic/DiagnosticDriverKinds.td
clang/include/clang/Basic/DiagnosticFrontendKinds.td
clang/include/clang/Driver/CommonArgs.h
clang/include/clang/Options/OptionUtils.h
clang/include/clang/Options/Options.td
clang/lib/CodeGen/CGHLSLRuntime.cpp
clang/lib/Driver/ToolChains/Clang.cpp
clang/lib/Driver/ToolChains/CommonArgs.cpp
clang/lib/Driver/ToolChains/Flang.cpp
clang/lib/Frontend/CompilerInvocation.cpp
clang/lib/Options/OptionUtils.cpp
clang/test/CodeGenHLSL/debug/source-language.hlsl
clang/test/CodeGenHLSL/lit.local.cfg
clang/test/Driver/dxc_debug.hlsl
clang/unittests/Driver/CMakeLists.txt
Removed:
################################################################################
diff --git a/clang/include/clang/Basic/CodeGenOptions.def b/clang/include/clang/Basic/CodeGenOptions.def
index 073daeeb4b87a..5f3baf771ff96 100644
--- a/clang/include/clang/Basic/CodeGenOptions.def
+++ b/clang/include/clang/Basic/CodeGenOptions.def
@@ -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)
diff --git a/clang/include/clang/Basic/CodeGenOptions.h b/clang/include/clang/Basic/CodeGenOptions.h
index 835ea728e9c56..97d68877467fd 100644
--- a/clang/include/clang/Basic/CodeGenOptions.h
+++ b/clang/include/clang/Basic/CodeGenOptions.h
@@ -268,6 +268,14 @@ class CodeGenOptions : public CodeGenOptionsBase {
/// 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
diff --git a/clang/include/clang/Basic/DiagnosticDriverKinds.td b/clang/include/clang/Basic/DiagnosticDriverKinds.td
index 8046db82d9ea8..6aba0622be193 100644
--- a/clang/include/clang/Basic/DiagnosticDriverKinds.td
+++ b/clang/include/clang/Basic/DiagnosticDriverKinds.td
@@ -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'">;
diff --git a/clang/include/clang/Basic/DiagnosticFrontendKinds.td b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
index 322ce2c3a75fd..058449ef47a46 100644
--- a/clang/include/clang/Basic/DiagnosticFrontendKinds.td
+++ b/clang/include/clang/Basic/DiagnosticFrontendKinds.td
@@ -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'">;
diff --git a/clang/include/clang/Driver/CommonArgs.h b/clang/include/clang/Driver/CommonArgs.h
index cdadb824a8ac3..14a83ff7beae4 100644
--- a/clang/include/clang/Driver/CommonArgs.h
+++ b/clang/include/clang/Driver/CommonArgs.h
@@ -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,
diff --git a/clang/include/clang/Options/OptionUtils.h b/clang/include/clang/Options/OptionUtils.h
index 02c9c27554db1..4305cdd134bf4 100644
--- a/clang/include/clang/Options/OptionUtils.h
+++ b/clang/include/clang/Options/OptionUtils.h
@@ -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
diff --git a/clang/include/clang/Options/Options.td b/clang/include/clang/Options/Options.td
index 84f2370a22301..a4b9cb802af4d 100644
--- a/clang/include/clang/Options/Options.td
+++ b/clang/include/clang/Options/Options.td
@@ -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]>,
diff --git a/clang/lib/CodeGen/CGHLSLRuntime.cpp b/clang/lib/CodeGen/CGHLSLRuntime.cpp
index 98e7b70d46672..40b29559c2a1c 100644
--- a/clang/lib/CodeGen/CGHLSLRuntime.cpp
+++ b/clang/lib/CodeGen/CGHLSLRuntime.cpp
@@ -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)
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index c2ac478d84929..312e8f8c69f0a 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -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
diff --git a/clang/lib/Driver/ToolChains/CommonArgs.cpp b/clang/lib/Driver/ToolChains/CommonArgs.cpp
index 2267d74ee7d58..48724746d9330 100644
--- a/clang/lib/Driver/ToolChains/CommonArgs.cpp
+++ b/clang/lib/Driver/ToolChains/CommonArgs.cpp
@@ -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,
diff --git a/clang/lib/Driver/ToolChains/Flang.cpp b/clang/lib/Driver/ToolChains/Flang.cpp
index fcbbcbcc7018b..629f56d6cf4a4 100644
--- a/clang/lib/Driver/ToolChains/Flang.cpp
+++ b/clang/lib/Driver/ToolChains/Flang.cpp
@@ -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");
diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp
index 47cdcad377d06..c564748ddabc1 100644
--- a/clang/lib/Frontend/CompilerInvocation.cpp
+++ b/clang/lib/Frontend/CompilerInvocation.cpp
@@ -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;
}
diff --git a/clang/lib/Options/OptionUtils.cpp b/clang/lib/Options/OptionUtils.cpp
index e5aefa012f679..77f89552e852a 100644
--- a/clang/lib/Options/OptionUtils.cpp
+++ b/clang/lib/Options/OptionUtils.cpp
@@ -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;
+}
diff --git a/clang/test/CodeGenHLSL/Inputs/a.hlsl b/clang/test/CodeGenHLSL/Inputs/a.hlsl
new file mode 100644
index 0000000000000..9c132672dea82
--- /dev/null
+++ b/clang/test/CodeGenHLSL/Inputs/a.hlsl
@@ -0,0 +1,10 @@
+#ifndef GUARD
+#define GUARD
+
+#include "b.hlsl"
+
+float helper1_add(float a, float b) {
+ return a + b;
+}
+
+#endif
diff --git a/clang/test/CodeGenHLSL/Inputs/b.hlsl b/clang/test/CodeGenHLSL/Inputs/b.hlsl
new file mode 100644
index 0000000000000..798909fa50569
--- /dev/null
+++ b/clang/test/CodeGenHLSL/Inputs/b.hlsl
@@ -0,0 +1,3 @@
+float helper2_mul(float a, float b) {
+ return a * b;
+}
diff --git a/clang/test/CodeGenHLSL/SysInputs/c.hlsl b/clang/test/CodeGenHLSL/SysInputs/c.hlsl
new file mode 100644
index 0000000000000..30223007e1920
--- /dev/null
+++ b/clang/test/CodeGenHLSL/SysInputs/c.hlsl
@@ -0,0 +1,3 @@
+float sys_helper(float a, float b) {
+ return a * b;
+}
diff --git a/clang/test/CodeGenHLSL/debug/source-language.hlsl b/clang/test/CodeGenHLSL/debug/source-language.hlsl
index f1e63d17724ee..83099b288853f 100644
--- a/clang/test/CodeGenHLSL/debug/source-language.hlsl
+++ b/clang/test/CodeGenHLSL/debug/source-language.hlsl
@@ -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,
diff --git a/clang/test/CodeGenHLSL/dx-source-metadata-disabled.hlsl b/clang/test/CodeGenHLSL/dx-source-metadata-disabled.hlsl
new file mode 100644
index 0000000000000..aa26a49feeef6
--- /dev/null
+++ b/clang/test/CodeGenHLSL/dx-source-metadata-disabled.hlsl
@@ -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;
+}
diff --git a/clang/test/CodeGenHLSL/dx-source-metadata-includes.hlsl b/clang/test/CodeGenHLSL/dx-source-metadata-includes.hlsl
new file mode 100644
index 0000000000000..65861f5a50fdc
--- /dev/null
+++ b/clang/test/CodeGenHLSL/dx-source-metadata-includes.hlsl
@@ -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);
+}
diff --git a/clang/test/CodeGenHLSL/dx-source-metadata-mailformed-command-line.hlsl b/clang/test/CodeGenHLSL/dx-source-metadata-mailformed-command-line.hlsl
new file mode 100644
index 0000000000000..8d964aaf573b4
--- /dev/null
+++ b/clang/test/CodeGenHLSL/dx-source-metadata-mailformed-command-line.hlsl
@@ -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;
+}
diff --git a/clang/test/CodeGenHLSL/dx-source-metadata.hlsl b/clang/test/CodeGenHLSL/dx-source-metadata.hlsl
new file mode 100644
index 0000000000000..c523f36cc69b6
--- /dev/null
+++ b/clang/test/CodeGenHLSL/dx-source-metadata.hlsl
@@ -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;
+}
diff --git a/clang/test/CodeGenHLSL/lit.local.cfg b/clang/test/CodeGenHLSL/lit.local.cfg
index 0604d1a83dc68..378a42883c9df 100644
--- a/clang/test/CodeGenHLSL/lit.local.cfg
+++ b/clang/test/CodeGenHLSL/lit.local.cfg
@@ -1 +1,2 @@
config.suffixes = [".c", ".hlsl"]
+config.excludes.add("SysInputs")
diff --git a/clang/test/Driver/dxc_debug.hlsl b/clang/test/Driver/dxc_debug.hlsl
index 0eab32168982b..d250e95a0bfae 100644
--- a/clang/test/Driver/dxc_debug.hlsl
+++ b/clang/test/Driver/dxc_debug.hlsl
@@ -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
diff --git a/clang/unittests/Driver/CMakeLists.txt b/clang/unittests/Driver/CMakeLists.txt
index fa0e87c3318df..fadaacf511291 100644
--- a/clang/unittests/Driver/CMakeLists.txt
+++ b/clang/unittests/Driver/CMakeLists.txt
@@ -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}
diff --git a/clang/unittests/Driver/EscapedCommandLineTest.cpp b/clang/unittests/Driver/EscapedCommandLineTest.cpp
new file mode 100644
index 0000000000000..bf4cffdf742d0
--- /dev/null
+++ b/clang/unittests/Driver/EscapedCommandLineTest.cpp
@@ -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);
+}
More information about the cfe-commits
mailing list