Skip to content

[Driver][DirectX] Add /Qembed_debug and /Fd flags#150

Open
kuilpd wants to merge 1 commit into
dx-debugfrom
kuilpd/add-embed-fd-flags
Open

[Driver][DirectX] Add /Qembed_debug and /Fd flags#150
kuilpd wants to merge 1 commit into
dx-debugfrom
kuilpd/add-embed-fd-flags

Conversation

@kuilpd
Copy link
Copy Markdown

@kuilpd kuilpd commented May 29, 2026

No description provided.

@kuilpd kuilpd requested a review from dzhidzhoev May 29, 2026 15:09
@dzhidzhoev
Copy link
Copy Markdown
Contributor

dzhidzhoev commented May 31, 2026

I have one test failure on Windows with this patch:

******************** TEST 'LLVM :: tools/dxil-dis/shuffle.ll' FAILED ********************
Exit Code: 2

Command Output (stdout):
--
# RUN: at line 1
d:\llvm\build\native\bin\llc.exe -start-after=dxil-flatten-arrays --filetype=obj D:\llvm\llvm-project\llvm\test\tools\dxil-dis\shuffle.ll -o - 2>&1 | d:\llvm\build\native\bin\dxil-dis.exe -o - | d:\llvm\build\native\bin\filecheck.exe D:\llvm\llvm-project\llvm\test\tools\dxil-dis\shuffle.ll
# executed command: 'd:\llvm\build\native\bin\llc.exe' -start-after=dxil-flatten-arrays --filetype=obj 'D:\llvm\llvm-project\llvm\test\tools\dxil-dis\shuffle.ll' -o -
# note: command had no output on stdout or stderr
# error: command failed with exit status: 0xc000001d
# executed command: 'd:\llvm\build\native\bin\dxil-dis.exe' -o -
# .---command stderr------------
# | d:\llvm\build\native\bin\dxil-dis.exe: error: Invalid bitcode size
# `-----------------------------
# error: command failed with exit status: 1
# executed command: 'd:\llvm\build\native\bin\filecheck.exe' 'D:\llvm\llvm-project\llvm\test\tools\dxil-dis\shuffle.ll'
# .---command stderr------------
# | FileCheck error: '<stdin>' is empty.
# | FileCheck command line:  d:\llvm\build\native\bin\filecheck.exe D:\llvm\llvm-project\llvm\test\tools\dxil-dis\shuffle.ll
# `-----------------------------
# error: command failed with exit status: 2

--

********************
Testing:  0.. 10.. 20.. 30.. 40.. 50.. 60.. 70.. 80.. 90..
********************
Failed Tests (1):
  LLVM :: tools/dxil-dis/shuffle.ll

Comment on lines +57 to +59
extern cl::opt<bool> EmbedDebug;
extern cl::opt<std::string> PdbDebugPath;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we actually need these lines?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I forgot to delete these when I experimented with checking these options in this file.

@@ -1,17 +0,0 @@
RUN: llc %S/Inputs/SourceInfo.ll -dx-Zss --filetype=obj -o %t.dxbc
Copy link
Copy Markdown
Contributor

@dzhidzhoev dzhidzhoev May 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this test getting removed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no output to PDB by default anymore, this exact same behavior is tested in test/CodeGen/DirectX/ContainerData/DebugName-user-directory.test now.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +142 to +143
if (!EmbedDebug && SectionName == "ILDB")
return true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More sections need to be filtered out (such as VERS). Please refer to the PDB doc.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERS is still output there when compiled as a library (which we do in all tests), I'm not sure how to check for this or if we even need to. The other sections are dependent on other flags, as I understand.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair point about VERS.
Yet SRCI should be always filtered out here.

Globals.emplace_back(
buildContainerGlobal(M, ModuleConstant, "dx.hash", "HASH"));

// Emit ILDN part in debug info mode.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We gotta move this comment closer to

  mcdxbc::DebugName DebugName;
  DebugName.setFilename(DebugNameStr);
  SmallString<64> ILDNData;
  raw_svector_ostream OS(ILDNData);
  DebugName.write(OS);
  addSection(M, Globals, ILDNData, "dx.ildn", "ILDN");

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do

Comment on lines +168 to +169
if (!EmbedDebug && PdbDebugPath.empty())
return;
Copy link
Copy Markdown
Contributor

@dzhidzhoev dzhidzhoev May 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ILDN (Shader debug name) part must be emitted when debug info is emitted, regardless of whether /Zi or /Zs is was used to trigger that.
I would just assume that if we have a DICompileUnit in module, then the module is produced by clang in debug info mode, which was triggered by passing /Zi or /Zs (as the main flags triggering debug info emission). And, in that case, debug name part should be created.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure where to put this yet, since we don't have a /Zs flag yet. This patch implements the logic for existing flags, so this can be changed as needed with /Zs patch.

Copy link
Copy Markdown
Contributor

@dzhidzhoev dzhidzhoev Jun 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, we only have /Zi, as for now.
ILDN must be emitted regardless of whether /Qembed_debug is passed, and regardless of whether /Fd is passed.
Can we simply emit it based on non-emptiness of M.debug_compile_units()? It's the indicator of whether /Zi was passed to clang-dxc.

Comment on lines +245 to +246
if (HasDebugInfo && !EmbedDebug && PdbDebugPath.empty())
EmbedDebug = true;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Modifying a cl::opt does not sound like a good idea to me. These variables are shared with other passes, and they are meant to represent user input. I couldn't find many examples where a pass modifies an option, other than in SampleProfileLoader pass initialization.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is the same check in the driver as well, if /Qembed_debug is not specified with /Zi present, I enable it for llc with -dx-embed-debug anyway, but it does write the warning (same as DXC does). So this check in the backend can only pass when llc is called directly, so I did this just to have the same default behavior. I can remove this, we'll just have to specify the option manually in the tests.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we can get rid of EmbedDebug usage here and in DXContainerGlobals. Thus, it will only affect MCDXContainerWriter. Then, EmbedDebug can be switched on, when needed, in MCDXContainerWriter. And we won't have to write something to cl::opt from a pass.

Comment on lines 275 to +276
SmallVector<GlobalValue *, 2> Globals;
if (HasDebugInfo) {
if (HasDebugInfo && (EmbedDebug || !PdbDebugPath.empty())) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider simlpifying this check, so that we don't have duplicate complex if conditions. Maybe, if (!ILDBData.empty()) will be enough?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do

Comment on lines +187 to +188
// If we don't want debug info, strip it from DXIL.
StripDebugInfo(M);
Copy link
Copy Markdown
Contributor

@dzhidzhoev dzhidzhoev May 31, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

StripDebugInfo is an expensive operation (scans all instructions etc). Do we really need to run it if M.debug_compile_units() is empty? Asking since HasDebugInfo argument has been removed.

For example, DebugInfo generation on other targets (DwarfDebug or CodeViewDebug) does not run at all if there are no compile units in a module. See the if condition in DebugHandlerBase::beginModule():

void DebugHandlerBase::beginModule(Module *M) {
  if (M->debug_compile_units().empty())
    Asm = nullptr;
  else
    ...
}

I believe we can correctly assume that, without DICompileUnits, no debug info is present at all. Otherwise, LLVM IR is not fully correct (if it has debug info without DICompileUnit, which is normally the root of debug information entries tree).

If it is still necessary to do that, let's make a separate commit which removes extra arguments from writeModule, or refactors the code of it in another way.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I joined the 2 arguments because they were the same when called. If HasDebugInfo was true, IsDebug was passed as true; if HasDebugInfo was false, IsDebug was passed as false. Basically, WriteDebug now implies that there is debug info present.

Comment on lines 1 to 9
// 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
// RUN: %clang_dxc -Tlib_6_7 -### -Zi -Qembed_debug %s 2>&1 | FileCheck %s
// RUN: %clang_dxc -Tlib_6_7 -### -Zi -gcodeview %s 2>&1 | FileCheck %s -check-prefixes=CHECK,CHECK-CV
// RUN: %clang_dxc -Tlib_6_7 -### -Zi -gdwarf %s 2>&1 | FileCheck %s -check-prefixes=CHECK,CHECK-DWARF
// RUN: %clang_dxc -Tlib_6_7 -### -gcodeview -Zi %s 2>&1 | FileCheck %s -check-prefixes=CHECK,CHECK-CV
// RUN: %clang_dxc -Tlib_6_7 -### -gdwarf -Zi %s 2>&1 | FileCheck %s -check-prefixes=CHECK,CHECK-DWARF
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add some checks for command lines created by Driver in presence of /Fd and/or /Qembed_debug. Either here on in a separate test.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright

Comment on lines 41 to +47

static cl::opt<std::string>
PdbFileName("dx-pdb-file",
cl::desc("Specify the PDB output file path for DirectX target"),
cl::value_desc("filename"));
static cl::opt<std::string> PdbOutputDir(
"dx-pdb-dir",
cl::desc("Specify the PDB output directory for DirectX target. The file "
"name is derived from the shader hash"),
cl::value_desc("directory"));
extern llvm::cl::opt<bool> EmbedDebug;
cl::opt<std::string> PdbDebugPath(
"dx-Fd",
cl::desc("Write debug information to the given file, or automatically "
"named file in directory when ending in '\\'"),
cl::value_desc("filename"));
Copy link
Copy Markdown
Contributor

@dzhidzhoev dzhidzhoev Jun 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@asavonic

/Fd is an option of original DXC to specify output PDB file name.
Its behavior depends on whether a path ends with path separator:

  1. If dxc invocation contains /Fd=something, PDB file named "something" is created.
  2. If it contains /Fd=something\ (argument value ends with \), DXC attempts to create PDB file named <MD5-hash-of-bitcode>.pdb in directory something\ (fstat is not called for the path to determine if it's a directory).

As of my knowledge, llc does not have any existing flag which has similar behavior (changing what we do depending on whether a directory path or a file path is provided as a value for the same argument).
Should we make two options for llc (like --dx-pdb-dir and --dx-pdb-file), with explicit case distinction, and implement the logic for choosing which llc invocation argument is right in Clang-dxc driver, or should we implement just one llc flag (--dx-Fd), and implement the abovementioned DXC logic here in llc?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we always generate MD5.pdb file in llc, and let the driver rename it if needed?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I implemented this logic in DXContainerGlobals.cpp:177-185. I chose to do this in the backend so that we would have the same arguments in both llc and the Driver to avoid confusion, and also so that if we needed a check to see if Fd was specified, we wouldn't have to check 2 options all the time.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we always generate MD5.pdb file in llc, and let the driver rename it if needed?

Then the driver would need to modify the content of llc's output files, because PDB file name is baked into PDB file and into the main llc output file.

@dzhidzhoev dzhidzhoev requested a review from asavonic June 1, 2026 00:17
Comment on lines -243 to +255
if (HasDebugInfo) {
if (HasDebugInfo && (EmbedDebug || !PdbDebugPath.empty())) {
Copy link
Copy Markdown
Contributor

@dzhidzhoev dzhidzhoev Jun 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The subcondition (EmbedDebug || !PdbDebugPath.empty()) is equivalent to true at this line, considering this assignment above:

    if (HasDebugInfo && !EmbedDebug && PdbDebugPath.empty())
      EmbedDebug = true;

Assuming HasDebugInfo is true, and execution reached line 255, the body of the if will always be executed.
I'd suggest we create ILDB regardless of flag presence, and do case distinction at later stages, if it is needed.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, will do

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants