[DirectX][Driver] Add /Qpdb_in_private flag support (#204903)

In DXC, when `/Qpdb_in_private` flag is specified, debug info PDB is
emitted into PRIV part of the output DXContainer (as well as into
separate PDB file, if its name is specified with `/Fd`).

This patch reimplements similar behavior in llc and Clang.

MC is modified to be able to emit PRIV part.

Depends on https://github.com/llvm/llvm-project/pull/204166.
This commit is contained in:
Vladislav Dzhidzhoev 2026-07-21 22:41:48 +02:00 committed by GitHub
parent 009e841dbd
commit db816fe2ed
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 267 additions and 23 deletions

View file

@ -9923,6 +9923,9 @@ def dxc_gis : DXCFlag<"Gis">,
def : Option<["/", "-"], "Qembed_debug", KIND_FLAG>, Group<dxc_Group>,
Flags<[Ignored]>, Visibility<[DXCOption]>,
HelpText<"Embed PDB in shader container (ignored)">;
def dxc_Qpdb_in_private : DXCFlag<"Qpdb_in_private">,
Flags<[HelpHidden]>,
HelpText<"Store PDB in private user data">;
def spirv : DXCFlag<"spirv">,
HelpText<"Generate SPIR-V code">;
def metal : DXCFlag<"metal">, HelpText<"Generate Metal library">;

View file

@ -4003,6 +4003,10 @@ static void RenderHLSLOptions(const Driver &D, const ArgList &Args,
CmdArgs.push_back("-mllvm");
CmdArgs.push_back("--dx-strip-debug");
}
if (Args.hasArg(options::OPT_dxc_Qpdb_in_private)) {
CmdArgs.push_back("-mllvm");
CmdArgs.push_back("--dx-pdb-in-private");
}
}
static void RenderOpenACCOptions(const Driver &D, const ArgList &Args,

View file

@ -3,6 +3,7 @@
// 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 /Qpdb_in_private %s 2>&1 | FileCheck %s -check-prefixes=CHECK,CHECK-PRIV
// RUN: %clang_dxc -Tlib_6_7 -### -Zi -Zss %s 2>&1 | FileCheck %s --check-prefix=CHECK,CHECK-ZSS
// RUN: %clang_dxc -Tlib_6_7 -### /Zi -Qsource_in_debug_module %s 2>&1 | FileCheck %s --check-prefix=CHECK,CHECK-SIDM
// RUN: %clang_dxc -Tlib_6_7 -### -Zi -Qstrip_debug %s 2>&1 | FileCheck %s --check-prefix=CHECK,CHECK-STRIP
@ -17,6 +18,7 @@
// Make sure dwarf-version is 4.
// CHECK-DWARF-SAME: -dwarf-version=4
// Check that the flags are converted to their llc equivalents.
// CHECK-PRIV-SAME: --dx-pdb-in-private
// CHECK-ZSS-SAME: -dx-Zss
// CHECK-SIDM-SAME: --dx-source-in-debug-module
// CHECK-STRIP-SAME: --dx-strip-debug

View file

@ -22,4 +22,15 @@
// CHECK: - Name: ILDN
// Check that /Qpdb_in_private emits a PRIV part in the output container.
// RUN: %clang_dxc -Tlib_6_7 /Fo %t-priv.dxbc /Zi /Qpdb_in_private %s 2>&1
// RUN: obj2yaml %t-priv.dxbc | FileCheck %s --check-prefix=CHECK-PRIV
// Without /Qpdb_in_private, PRIV is not emitted.
// RUN: %clang_dxc -Tlib_6_7 /Fo %t-no-priv.dxbc /Zi %s 2>&1
// RUN: obj2yaml %t-no-priv.dxbc | FileCheck %s --check-prefix=CHECK-NO-PRIV
// CHECK-PRIV: - Name: PRIV
// CHECK-NO-PRIV-NOT: - Name: PRIV
[numthreads(1, 1, 1)] void main() {}

View file

@ -49,14 +49,22 @@ void MCDXContainerBaseWriter::write(raw_ostream &OS, const Triple &TT) {
// 16 part offsets gives us a little room for growth.
llvm::SmallVector<uint64_t, 16> PartOffsets;
uint64_t PartOffset = 0;
bool HasPrivate = false;
for (const MCDXContainerPart &Part : Parts) {
if (HasPrivate)
reportFatalInternalError(
"PRIV must be the last section in a DXContainer");
if (Part.Name == "PRIV")
HasPrivate = true;
uint64_t SectionSize = Part.Data.size();
assert(SectionSize < std::numeric_limits<uint32_t>::max() &&
"Section size too large for DXContainer");
PartOffsets.push_back(PartOffset);
PartOffset += sizeof(dxbc::PartHeader) + SectionSize;
PartOffset = alignTo(PartOffset, Align(4ul));
if (!HasPrivate)
PartOffset = alignTo(PartOffset, Align(4ul));
// The DXIL part also writes a program header, so we need to include its
// size when computing the offset for a part after the DXIL part.
if (dxbc::isProgramPart(Part.Name))
@ -96,8 +104,10 @@ void MCDXContainerBaseWriter::write(raw_ostream &OS, const Triple &TT) {
if (dxbc::isProgramPart(Part.Name))
PartSize += sizeof(dxbc::ProgramHeader);
// DXContainer parts should be 4-byte aligned.
PartSize = alignTo(PartSize, Align(4));
// DXContainer part should be 4-byte aligned, unless it is PRIV part.
bool IsPrivate = Part.Name == "PRIV";
if (!IsPrivate)
PartSize = alignTo(PartSize, Align(4));
W.write<uint32_t>(static_cast<uint32_t>(PartSize));
if (dxbc::isProgramPart(Part.Name)) {
dxbc::ProgramHeader Header;
@ -127,8 +137,10 @@ void MCDXContainerBaseWriter::write(raw_ostream &OS, const Triple &TT) {
sizeof(dxbc::ProgramHeader)));
}
W.write<char>(Part.Data);
unsigned Size = W.OS.tell() - Start;
W.OS.write_zeros(offsetToAlignment(Size, Align(4)));
if (!IsPrivate) {
unsigned Size = W.OS.tell() - Start;
W.OS.write_zeros(offsetToAlignment(Size, Align(4)));
}
}
}

View file

@ -43,6 +43,8 @@ static cl::opt<bool> ShaderHashDependsOnSource(
"dx-Zss", cl::desc("Compute Shader Hash considering source information"));
extern cl::opt<std::string> PdbDebugPath;
extern cl::opt<bool> SourceInDebugModule;
cl::opt<bool> PdbInPrivate("dx-pdb-in-private",
cl::desc("Store PDB in private user data"));
namespace {
class DXContainerGlobals : public llvm::ModulePass {
@ -159,22 +161,26 @@ void DXContainerGlobals::computeShaderHashAndDebugName(
SmallString<40> DebugNameStr;
Digest.stringifyResult(MD5, DebugNameStr);
DebugNameStr += ".pdb";
if (!PdbDebugPath.empty()) {
StringRef DebugFile = PdbDebugPath.getValue();
SmallString<256> AbsoluteDebugName;
if (sys::path::is_separator(DebugFile.back())) {
// If /Fd was specified as a directory, put the MD5.pdb file there.
AbsoluteDebugName = DebugFile;
sys::path::append(AbsoluteDebugName, DebugNameStr);
} else {
// Otherwise, use /Fd value as a user-provided PDB file name.
DebugNameStr = DebugFile;
AbsoluteDebugName = DebugNameStr;
if (!PdbDebugPath.empty() || PdbInPrivate) {
if (!PdbDebugPath.empty()) {
StringRef DebugFile = PdbDebugPath.getValue();
SmallString<256> AbsoluteDebugName;
if (sys::path::is_separator(DebugFile.back())) {
// If PDB output path was specified as a directory, put the MD5.pdb file
// there.
AbsoluteDebugName = DebugFile;
sys::path::append(AbsoluteDebugName, DebugNameStr);
} else {
// Otherwise, use PDB output path as a user-provided PDB file name.
DebugNameStr = DebugFile;
AbsoluteDebugName = DebugNameStr;
}
// Pass PDB name to DXContainerPDBPass via PDBNAME section.
addSection(M, Globals, AbsoluteDebugName, "dx.pdb.name",
PdbFileNameSectionName);
}
// Pass PDB name to DXContainerPDBPass via PDBNAME section.
addSection(M, Globals, AbsoluteDebugName, "dx.pdb.name",
PdbFileNameSectionName);
// Pass module hash to DXContainerPDBPass.
Globals.emplace_back(buildContainerGlobal(
M, ConstantDataArray::get(M.getContext(), ArrayRef(HashData.Digest)),

View file

@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "DirectX.h"
#include "llvm/ADT/ScopeExit.h"
#include "llvm/ADT/StringSet.h"
#include "llvm/BinaryFormat/DXContainer.h"
#include "llvm/DebugInfo/CodeView/GUID.h"
@ -17,10 +18,15 @@
#include "llvm/IR/Module.h"
#include "llvm/MC/MCDXContainerWriter.h"
#include "llvm/Pass.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/IOSandbox.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"
using namespace llvm;
extern cl::opt<bool> PdbInPrivate;
namespace {
class DXContainerPDB : public ModulePass, MCDXContainerBaseWriter {
@ -85,10 +91,22 @@ ArrayRef<MCDXContainerPart> DXContainerPDB::collectParts() {
return Parts;
}
static GlobalVariable *createPrivateDataGlobal(Module &M, StringRef Data) {
Constant *Content =
ConstantDataArray::getString(M.getContext(), Data, /*AddNull*/ false);
auto *GV =
new GlobalVariable(M, Content->getType(), true,
GlobalValue::PrivateLinkage, Content, "dx.priv");
GV->setSection("PRIV");
GV->setAlignment(Align(1));
return GV;
}
bool DXContainerPDB::runOnModule(Module &M) {
llvm::scope_exit Cleanup([&]() { reset(); });
this->M = &M;
StringRef DebugFileName;
SmallString<128> DebugFileName;
ArrayRef<char> ModuleHash;
for (const GlobalVariable &GV : M.globals()) {
if (GV.getSection() == PdbFileNameSectionName) {
@ -102,11 +120,23 @@ bool DXContainerPDB::runOnModule(Module &M) {
}
// PDB emission was not requested.
if (DebugFileName.empty())
if (DebugFileName.empty() && !PdbInPrivate)
return false;
if (ModuleHash.empty())
report_fatal_error("Module hash for PDB not found");
bool DeleteAfterRead = false;
if (DebugFileName.empty()) {
if (std::error_code EC =
sys::fs::createTemporaryFile("dxil", "pdb", DebugFileName))
reportFatalInternalError("Failed to create temporary PDB file");
DeleteAfterRead = true;
}
llvm::scope_exit FileCleanup([&]() {
if (DeleteAfterRead)
sys::fs::remove(DebugFileName);
});
BumpPtrAllocator Allocator;
pdb::PDBFileBuilder Builder(Allocator);
@ -151,9 +181,17 @@ bool DXContainerPDB::runOnModule(Module &M) {
reportFatalUsageError("Couldn't write to PDB file: " +
Twine(toString(std::move(Err))));
reset();
if (!PdbInPrivate)
return false;
return false;
ErrorOr<std::unique_ptr<MemoryBuffer>> Buf = MemoryBuffer::getFile(
DebugFileName, /*IsText=*/false, /*RequiresNullTerminator=*/false);
if (!Buf)
reportFatalInternalError("Failed to read PDB for PRIV embedding");
appendToCompilerUsed(M, createPrivateDataGlobal(M, (*Buf)->getBuffer()));
return true;
}
char DXContainerPDB::ID = 0;

View file

@ -0,0 +1,17 @@
## Check that --dx-pdb-in-private generates a PDB in PRIV without --dx-pdb-path.
# RUN: llc %S/Inputs/SourceInfo.ll --filetype=obj -o %t.dxbc --dx-pdb-in-private
# RUN: obj2yaml %t.dxbc | FileCheck %s
# RUN: llvm-objcopy --dump-section=PRIV=%t.priv %t.dxbc
# RUN: llvm-pdbutil pdb2yaml --dxcontainer %t.priv | FileCheck %s --check-prefix=PDB
# CHECK: - Name: PRIV
# CHECK-NEXT: Size:
# PDB: PartCount: 5
# PDB: Parts:
# PDB-DAG: - Name: ILDB
# PDB-DAG: - Name: HASH
# PDB-DAG: - Name: ILDN
# PDB-DAG: - Name: VERS
# PDB-DAG: - Name: SRCI

View file

@ -0,0 +1,5 @@
## Check that PRIV is not emitted unless --dx-pdb-in-private is specified.
# RUN: llc %S/Inputs/SourceInfo.ll --filetype=obj -o - | obj2yaml | FileCheck %s --implicit-check-not='PRIV'
# CHECK: - Name: DXIL

View file

@ -0,0 +1,10 @@
;; Check that dxil-pdb can emit PRIV using a temporary PDB when PDBNAME is absent.
; RUN: opt %s -dxil-pdb --dx-pdb-in-private -S -o - | FileCheck %s
;; CHECK: @dx.priv = private constant {{.*}} section "PRIV"
target triple = "dxilv1.3-pc-shadermodel6.3-library"
@dx.ildb = private constant [4 x i8] c"BC\C0\DE", section "ILDB", align 4
@dx.pdb.hash = private constant [16 x i8] c"dummymodulehash!", section "PDBHASH", align 4
@llvm.compiler.used = appending global [2 x ptr] [ptr @dx.ildb, ptr @dx.pdb.hash], section "llvm.metadata"

View file

@ -0,0 +1,18 @@
; RUN: opt %s -dxil-pdb --dx-pdb-in-private -S -o - | FileCheck %s --check-prefix=CHECK-IR
; RUN: opt %s -dxil-pdb --dx-pdb-in-private -o /dev/null
; RUN: llvm-pdbutil pdb2yaml --dxcontainer PdbInPrivateTest.pdb | FileCheck %s --check-prefix=CHECK-PDB
;; Check that dxil-pdb pass emits a PRIV global when --dx-pdb-in-private is set.
; CHECK-IR: @dx.priv = private constant {{.*}} section "PRIV"
; Check that the companion PDB file is still written when PDBNAME is present.
; CHECK-PDB: PartCount: 1
; CHECK-PDB: Parts:
; CHECK-PDB: - Name: ILDB
target triple = "dxilv1.3-pc-shadermodel6.3-library"
@dx.ildb = private constant [4 x i8] c"BC\C0\DE", section "ILDB", align 4
@dx.pdb.name = private constant [20 x i8] c"PdbInPrivateTest.pdb", section "PDBNAME", align 4
@dx.pdb.hash = private constant [16 x i8] c"dummymodulehash!", section "PDBHASH", align 4
@llvm.compiler.used = appending global [3 x ptr] [ptr @dx.ildb, ptr @dx.pdb.name, ptr @dx.pdb.hash], section "llvm.metadata"

View file

@ -0,0 +1,33 @@
## Check that --dx-pdb-in-private stores the generated PDB in the PRIV part.
## File: --dx-pdb-path specifies the PDB output path.
# RUN: llc %S/Inputs/SourceInfo.ll --filetype=obj -o %t.dxbc --dx-pdb-in-private --dx-pdb-path=%t.pdb
# RUN: obj2yaml %t.dxbc | FileCheck %s
# RUN: llvm-objcopy --dump-section=PRIV=%t.priv %t.dxbc
# RUN: diff %t.pdb %t.priv
# RUN: llvm-pdbutil pdb2yaml --dxcontainer %t.priv | FileCheck %s --check-prefix=PDB
## Directory: --dx-pdb-path ending in '/' writes <md5>.pdb under that directory.
# RUN: rm -rf %t.dir && mkdir %t.dir
# RUN: llc %S/Inputs/SourceInfo.ll --filetype=obj -o %t.dir.dxbc --dx-pdb-in-private --dx-pdb-path=%t.dir/
# RUN: obj2yaml %t.dir.dxbc | FileCheck %s
# RUN: llvm-objcopy --dump-section=PRIV=%t.dir.priv %t.dir.dxbc
# RUN: llvm-objcopy --dump-section=DXIL=%t.dir.bc %t.dir.dxbc
# RUN: %md5sum %t.dir.bc >%t.dir.bc.md5
# RUN: %python %S/Inputs/check_pdb_exists.py %t.dir %t.dir.bc.md5
# RUN: cat %t.dir/*.pdb > %t.dir.pdb
# RUN: diff %t.dir.pdb %t.dir.priv
# RUN: llvm-pdbutil pdb2yaml --dxcontainer %t.dir.priv | FileCheck %s --check-prefix=PDB
# CHECK: - Name: PRIV
# CHECK-NEXT: Size:
# PDB: PartCount: 5
# PDB: Parts:
# PDB-DAG: - Name: ILDB
# PDB-DAG: - Name: HASH
# PDB-DAG: - Name: ILDN
# PDB-DAG: - Name: VERS
# PDB-DAG: - Name: SRCI

View file

@ -6,6 +6,7 @@ endforeach()
set(LLVM_LINK_COMPONENTS
${LLVM_TARGETS_TO_BUILD}
BinaryFormat
DebugInfoDWARF
MC
MCDisassembler
@ -19,6 +20,7 @@ add_llvm_unittest(MCTests
DwarfDebugFrameCIE.cpp
DwarfLineTables.cpp
DwarfLineTableHeaders.cpp
DXContainerWriterTest.cpp
MCInstPrinter.cpp
StringTableBuilderTest.cpp
TargetRegistry.cpp

View file

@ -0,0 +1,83 @@
//===- DXContainerWriterTest.cpp - MCDXContainerWriter tests --------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
#include "llvm/MC/MCDXContainerWriter.h"
#include "llvm/Object/DXContainer.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/MemoryBufferRef.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/TargetParser/Triple.h"
#include "gtest/gtest.h"
using namespace llvm;
using namespace llvm::object;
namespace {
class TestDXContainerWriter : public MCDXContainerBaseWriter {
SmallVector<SmallString<8>> PartNames;
SmallVector<SmallString<32>> PartData;
SmallVector<MCDXContainerPart> Parts;
protected:
ArrayRef<MCDXContainerPart> collectParts() override { return Parts; }
public:
void addPart(StringRef Name, ArrayRef<uint8_t> Data) {
PartNames.emplace_back(Name);
PartData.emplace_back(Data.begin(), Data.end());
Parts.push_back({PartNames.back(), PartData.back()});
}
};
static Triple getTestTriple() {
return Triple("dxilv1.3-pc-shadermodel6.3-library");
}
static std::string writeContainer(TestDXContainerWriter &Writer) {
std::string Buffer;
raw_string_ostream OS(Buffer);
Writer.write(OS, getTestTriple());
return Buffer;
}
static DXContainer parseContainer(StringRef Buffer) {
return llvm::cantFail(DXContainer::create(MemoryBufferRef(Buffer, "")));
}
TEST(MCDXContainerWriterTest, PrivUnaligned) {
TestDXContainerWriter Writer;
const uint8_t PrivData[] = {0xDE, 0xAD, 0xBE, 0xEF, 0x42};
Writer.addPart("PRIV", PrivData);
std::string Buffer = writeContainer(Writer);
DXContainer C = parseContainer(Buffer);
EXPECT_EQ(C.getHeader().PartCount, 1u);
EXPECT_EQ(C.getHeader().FileSize, 49u);
EXPECT_EQ(C.getData().size(), 49u);
ASSERT_TRUE(C.getPrivateData());
EXPECT_EQ(C.getPrivateData()->size(), 5u);
EXPECT_EQ(
*C.getPrivateData(),
StringRef(reinterpret_cast<const char *>(PrivData), sizeof(PrivData)));
}
TEST(MCDXContainerWriterTest, PrivMustBeLast) {
TestDXContainerWriter Writer;
const uint8_t PrivData[] = {0x42};
const uint8_t DxilData[] = {0xBC, 0xC0, 0xDE, 0x00};
Writer.addPart("PRIV", PrivData);
Writer.addPart("DXIL", DxilData);
EXPECT_DEATH(writeContainer(Writer),
"PRIV must be the last section in a DXContainer");
}
} // namespace