[llvm] [DirectX][ObjectYAML] Add PRIV part support (PR #204899)
Vladislav Dzhidzhoev via llvm-commits
llvm-commits at lists.llvm.org
Fri Jun 19 16:10:29 PDT 2026
https://github.com/dzhidzhoev created https://github.com/llvm/llvm-project/pull/204899
Add support for DXContainer PRIV in the ObjectYAML pipeline so it can be represented in structured YAML and round-tripped through yaml2obj/obj2yaml.
PRIV part can store arbitrary user-provided binary blobs in DXContainer. Unlike other DXContainer parts, PRIV part does not have to be 4-byte aligned. Therefore, if it is present, it is always the last section in a DXContainer.
llvm-objcopy is already able to extract PRIV section. A test to verify extraction of binary from PRIV is added.
>From 8e53dabc9657447dccb277227ff4171cb71311e1 Mon Sep 17 00:00:00 2001
From: Vladislav Dzhidzhoev <vdzhidzhoev at accesssoftek.com>
Date: Wed, 17 Jun 2026 17:00:52 +0200
Subject: [PATCH] [DirectX][ObjectYAML] Add PRIV part support
Add support for DXContainer PRIV in the ObjectYAML pipeline so it can be
represented in structured YAML and round-tripped through
yaml2obj/obj2yaml.
PRIV part can store arbitrary user-provided binary blobs in DXContainer.
Unlike other DXContainer parts, PRIV part does not have to be 4-byte
aligned. Therefore, if it is present, it is always the last section
in a DXContainer.
llvm-objcopy is already able to extract PRIV section. A test to verify
extraction of binary from PRIV is added.
---
.../BinaryFormat/DXContainerConstants.def | 1 +
llvm/include/llvm/Object/DXContainer.h | 4 ++
.../include/llvm/ObjectYAML/DXContainerYAML.h | 1 +
llvm/lib/Object/DXContainer.cpp | 14 +++++++
llvm/lib/ObjectYAML/DXContainerEmitter.cpp | 15 +++++++
llvm/lib/ObjectYAML/DXContainerYAML.cpp | 9 ++++
.../ObjectYAML/DXContainer/PRIV-not-last.yaml | 29 +++++++++++++
.../DXContainer/dump-section-priv.yaml | 35 ++++++++++++++++
.../tools/obj2yaml/DXContainer/PRIVPart.yaml | 42 +++++++++++++++++++
llvm/unittests/Object/DXContainerTest.cpp | 30 +++++++++++++
.../ObjectYAML/DXContainerYAMLTest.cpp | 29 +++++++++++++
11 files changed, 209 insertions(+)
create mode 100644 llvm/test/ObjectYAML/DXContainer/PRIV-not-last.yaml
create mode 100644 llvm/test/tools/llvm-objcopy/DXContainer/dump-section-priv.yaml
create mode 100644 llvm/test/tools/obj2yaml/DXContainer/PRIVPart.yaml
diff --git a/llvm/include/llvm/BinaryFormat/DXContainerConstants.def b/llvm/include/llvm/BinaryFormat/DXContainerConstants.def
index 419e78c8a87d4..a6f8aaad6487f 100644
--- a/llvm/include/llvm/BinaryFormat/DXContainerConstants.def
+++ b/llvm/include/llvm/BinaryFormat/DXContainerConstants.def
@@ -3,6 +3,7 @@
CONTAINER_PART(DXIL)
CONTAINER_PART(ILDB)
CONTAINER_PART(ILDN)
+CONTAINER_PART(PRIV)
CONTAINER_PART(SFI0)
CONTAINER_PART(SRCI)
CONTAINER_PART(HASH)
diff --git a/llvm/include/llvm/Object/DXContainer.h b/llvm/include/llvm/Object/DXContainer.h
index ce3b9bc94ba97..545e8930f1458 100644
--- a/llvm/include/llvm/Object/DXContainer.h
+++ b/llvm/include/llvm/Object/DXContainer.h
@@ -480,6 +480,7 @@ class DXContainer {
std::optional<mcdxbc::DebugName> DebugName;
std::optional<mcdxbc::CompilerVersion> VersionInfo;
std::optional<mcdxbc::SourceInfo> SourceInfo;
+ std::optional<StringRef> PrivateData;
Error parseHeader();
Error parsePartOffsets();
@@ -492,6 +493,7 @@ class DXContainer {
Error parseSignature(StringRef Part, DirectX::Signature &Array);
Error parseCompilerVersionInfo(StringRef Part);
Error parseSourceInfo(StringRef Part);
+ Error parsePrivateData(StringRef Part);
friend class PartIterator;
public:
@@ -613,6 +615,8 @@ class DXContainer {
const std::optional<mcdxbc::SourceInfo> &getSourceInfo() const {
return SourceInfo;
}
+
+ const std::optional<StringRef> &getPrivateData() const { return PrivateData; }
};
class LLVM_ABI DXContainerObjectFile : public ObjectFile {
diff --git a/llvm/include/llvm/ObjectYAML/DXContainerYAML.h b/llvm/include/llvm/ObjectYAML/DXContainerYAML.h
index 29f6b9bcae000..1d3e044bbb22f 100644
--- a/llvm/include/llvm/ObjectYAML/DXContainerYAML.h
+++ b/llvm/include/llvm/ObjectYAML/DXContainerYAML.h
@@ -401,6 +401,7 @@ struct Part {
std::optional<DXContainerYAML::DebugName> DebugName;
std::optional<DXContainerYAML::CompilerVersion> CompilerVersion;
std::optional<DXContainerYAML::SourceInfo> SourceInfo;
+ std::optional<std::vector<llvm::yaml::Hex8>> PrivateData;
};
struct Object {
diff --git a/llvm/lib/Object/DXContainer.cpp b/llvm/lib/Object/DXContainer.cpp
index 52d3c3780bf71..947a0f32776d5 100644
--- a/llvm/lib/Object/DXContainer.cpp
+++ b/llvm/lib/Object/DXContainer.cpp
@@ -127,6 +127,13 @@ Error DXContainer::parseDebugName(StringRef Part) {
return Error::success();
}
+Error DXContainer::parsePrivateData(StringRef Part) {
+ if (PrivateData)
+ return parseFailed("more than one PRIV part is present in the file");
+ PrivateData.emplace(Part);
+ return Error::success();
+}
+
Error DXContainer::parseShaderFeatureFlags(StringRef Part) {
if (ShaderFeatureFlags)
return parseFailed("More than one SFI0 part is present in the file");
@@ -536,6 +543,9 @@ Error DXContainer::parsePartOffsets() {
sizeof(dxbc::Header) + (Header.PartCount * sizeof(uint32_t));
const char *Current = Data.getBuffer().data() + sizeof(dxbc::Header);
for (uint32_t Part = 0; Part < Header.PartCount; ++Part) {
+ if (PrivateData)
+ return parseFailed("PRIV must be the last section in a DXContainer");
+
uint32_t PartOffset;
if (Error Err = readInteger(Data.getBuffer(), Current, PartOffset))
return Err;
@@ -577,6 +587,10 @@ Error DXContainer::parsePartOffsets() {
if (Error Err = parseDebugName(PartData))
return Err;
break;
+ case dxbc::PartType::PRIV:
+ if (Error Err = parsePrivateData(PartData))
+ return Err;
+ break;
case dxbc::PartType::SFI0:
if (Error Err = parseShaderFeatureFlags(PartData))
return Err;
diff --git a/llvm/lib/ObjectYAML/DXContainerEmitter.cpp b/llvm/lib/ObjectYAML/DXContainerEmitter.cpp
index ff43eb71d57c7..5b0639a012e12 100644
--- a/llvm/lib/ObjectYAML/DXContainerEmitter.cpp
+++ b/llvm/lib/ObjectYAML/DXContainerEmitter.cpp
@@ -125,9 +125,15 @@ assignSectionHeader(dxbc::SourceInfo::SectionHeader &Dst,
}
Error DXContainerWriter::writeParts(raw_ostream &OS) {
+ bool HasPrivate = false;
uint32_t RollingOffset =
sizeof(dxbc::Header) + (ObjectFile.Header.PartCount * sizeof(uint32_t));
for (auto I : llvm::zip(ObjectFile.Parts, *ObjectFile.Header.PartOffsets)) {
+ if (HasPrivate)
+ return createStringError(
+ errc::invalid_argument,
+ "PRIV must be the last section in a DXContainer");
+
if (RollingOffset < std::get<1>(I)) {
uint32_t PadBytes = std::get<1>(I) - RollingOffset;
OS.write_zeros(PadBytes);
@@ -205,6 +211,15 @@ Error DXContainerWriter::writeParts(raw_ostream &OS) {
DebugName.write(OS);
break;
}
+ case dxbc::PartType::PRIV: {
+ if (!P.PrivateData)
+ continue;
+
+ HasPrivate = true;
+ OS.write(reinterpret_cast<char *>(P.PrivateData->data()),
+ P.PrivateData->size());
+ break;
+ }
case dxbc::PartType::SFI0: {
// If we don't have any flags we can continue here and the data will be
// zeroed out.
diff --git a/llvm/lib/ObjectYAML/DXContainerYAML.cpp b/llvm/lib/ObjectYAML/DXContainerYAML.cpp
index ba766dfffd91d..1671ec2afed9b 100644
--- a/llvm/lib/ObjectYAML/DXContainerYAML.cpp
+++ b/llvm/lib/ObjectYAML/DXContainerYAML.cpp
@@ -567,6 +567,7 @@ void MappingTraits<DXContainerYAML::Part>::mapping(IO &IO,
IO.mapOptional("DebugName", P.DebugName);
IO.mapOptional("CompilerVersion", P.CompilerVersion);
IO.mapOptional("SourceInfo", P.SourceInfo);
+ IO.mapOptional("PrivateData", P.PrivateData);
}
void MappingTraits<DXContainerYAML::Object>::mapping(
@@ -981,6 +982,14 @@ DXContainerYAML::fromDXContainer(object::DXContainer &Container) {
DebugName->Filename.str()};
break;
}
+ case dxbc::PartType::PRIV: {
+ std::optional<StringRef> PrivateData = Container.getPrivateData();
+ assert(PrivateData && "Since we are iterating and found a PRIV part, "
+ "this should never not have a value");
+ NewPart.PrivateData.emplace(PrivateData->data(),
+ PrivateData->data() + PrivateData->size());
+ break;
+ }
case dxbc::PartType::SFI0: {
std::optional<uint64_t> Flags = Container.getShaderFeatureFlags();
// Omit the flags in the YAML if they are missing or zero.
diff --git a/llvm/test/ObjectYAML/DXContainer/PRIV-not-last.yaml b/llvm/test/ObjectYAML/DXContainer/PRIV-not-last.yaml
new file mode 100644
index 0000000000000..f573935a731af
--- /dev/null
+++ b/llvm/test/ObjectYAML/DXContainer/PRIV-not-last.yaml
@@ -0,0 +1,29 @@
+## Check that ObjectYAML rejects PRIV parts that are not the last section.
+
+# RUN: not yaml2obj %s 2>&1 | FileCheck %s
+
+# CHECK: PRIV must be the last section in a DXContainer
+
+--- !dxcontainer
+Header:
+ Hash: [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ]
+ Version:
+ Major: 1
+ Minor: 0
+ PartCount: 2
+Parts:
+ - Name: PRIV
+ Size: 5
+ PrivateData: [ 0xDE, 0xAD, 0xBE, 0xEF, 0x42 ]
+ - Name: DXIL
+ Size: 24
+ Program:
+ MajorVersion: 6
+ MinorVersion: 0
+ ShaderKind: 5
+ Size: 6
+ DXILMajorVersion: 0
+ DXILMinorVersion: 1
+ DXILSize: 0
+...
diff --git a/llvm/test/tools/llvm-objcopy/DXContainer/dump-section-priv.yaml b/llvm/test/tools/llvm-objcopy/DXContainer/dump-section-priv.yaml
new file mode 100644
index 0000000000000..a145c45eefc1f
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/DXContainer/dump-section-priv.yaml
@@ -0,0 +1,35 @@
+## Check that --dump-section exports the raw PRIV part data, including when the
+## part size is not a multiple of 4.
+
+# RUN: yaml2obj %s -o %t.dxbc
+# RUN: llvm-objcopy --dump-section=PRIV=%t.priv %t.dxbc
+
+# RUN: wc -c %t.priv | FileCheck %s --check-prefix=PRIV-SIZE
+# PRIV-SIZE: 5
+
+# RUN: od -t x1 %t.priv | FileCheck %s --ignore-case
+# CHECK: de ad be ef 42
+
+--- !dxcontainer
+Header:
+ Hash: [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ]
+ Version:
+ Major: 1
+ Minor: 0
+ PartCount: 2
+Parts:
+ - Name: DXIL
+ Size: 24
+ Program:
+ MajorVersion: 6
+ MinorVersion: 0
+ ShaderKind: 5
+ Size: 6
+ DXILMajorVersion: 0
+ DXILMinorVersion: 1
+ DXILSize: 0
+ - Name: PRIV
+ Size: 5
+ PrivateData: [ 0xDE, 0xAD, 0xBE, 0xEF, 0x42 ]
+...
diff --git a/llvm/test/tools/obj2yaml/DXContainer/PRIVPart.yaml b/llvm/test/tools/obj2yaml/DXContainer/PRIVPart.yaml
new file mode 100644
index 0000000000000..0d429a1381413
--- /dev/null
+++ b/llvm/test/tools/obj2yaml/DXContainer/PRIVPart.yaml
@@ -0,0 +1,42 @@
+## Check that the PRIV part round-trips through yaml2obj and obj2yaml when the
+## part size is not a multiple of 4. The pipeline is run twice to ensure obj2yaml
+## output with a non-multiple-of-4 FileSize can be fed back through yaml2obj.
+
+# RUN: yaml2obj %s 2>&1 | obj2yaml 2>&1 | yaml2obj %s 2>&1 | obj2yaml 2>&1 | FileCheck %s
+
+--- !dxcontainer
+Header:
+ Hash: [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ]
+ Version:
+ Major: 1
+ Minor: 0
+ PartCount: 2
+Parts:
+ - Name: DXIL
+ Size: 24
+ Program:
+ MajorVersion: 6
+ MinorVersion: 0
+ ShaderKind: 5
+ Size: 6
+ DXILMajorVersion: 0
+ DXILMinorVersion: 1
+ DXILSize: 0
+ - Name: PRIV
+ Size: 5
+ PrivateData: [ 0xDE, 0xAD, 0xBE, 0xEF, 0x42 ]
+...
+
+# CHECK: FileSize: 85
+# CHECK: PartCount: 2
+# CHECK: PartOffsets: [ 40, 72 ]
+# CHECK: Parts:
+# CHECK: - Name: DXIL
+# CHECK: Program:
+# CHECK: MajorVersion: 6
+# CHECK: MinorVersion: 0
+# CHECK: ShaderKind: 5
+# CHECK: - Name: PRIV
+# CHECK: Size: 5
+# CHECK: PrivateData: [ 0xDE, 0xAD, 0xBE, 0xEF, 0x42 ]
diff --git a/llvm/unittests/Object/DXContainerTest.cpp b/llvm/unittests/Object/DXContainerTest.cpp
index 0051839d071e7..9eefc1be296b5 100644
--- a/llvm/unittests/Object/DXContainerTest.cpp
+++ b/llvm/unittests/Object/DXContainerTest.cpp
@@ -320,6 +320,36 @@ TEST(DXCFile, ParseILDNPart) {
EXPECT_EQ(ILDN->Filename, "abc.pdb");
}
+// This test verifies that PRIV part is correctly parsed.
+// This test is based on the binary output constructed from this yaml.
+// --- !dxcontainer
+// Header:
+// Hash: [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+// 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ]
+// Version:
+// Major: 1
+// Minor: 0
+// PartCount: 1
+// Parts:
+// - Name: PRIV
+// Size: 5
+// PrivateData: [ 0xDE, 0xAD, 0xBE, 0xEF, 0x42 ]
+// ...
+TEST(DXCFile, ParsePRIVPart) {
+ uint8_t Buffer[] = {0x44, 0x58, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x50, 0x52, 0x49, 0x56, 0x05, 0x00, 0x00, 0x00, 0xDE,
+ 0xAD, 0xBE, 0xEF, 0x42};
+ DXContainer C =
+ llvm::cantFail(DXContainer::create(getMemoryBuffer<49>(Buffer)));
+ EXPECT_EQ(C.getHeader().PartCount, 1u);
+ const std::optional<StringRef> &PrivateData = C.getPrivateData();
+ EXPECT_TRUE(PrivateData.has_value());
+ EXPECT_EQ(*PrivateData, "\xDE\xAD\xBE\xEF\x42");
+}
+
// This test verifies that VERS part is correctly parsed.
// This test is based on the binary output constructed from this yaml.
// --- !dxcontainer
diff --git a/llvm/unittests/ObjectYAML/DXContainerYAMLTest.cpp b/llvm/unittests/ObjectYAML/DXContainerYAMLTest.cpp
index 1857f10ffdae1..57515450c59ac 100644
--- a/llvm/unittests/ObjectYAML/DXContainerYAMLTest.cpp
+++ b/llvm/unittests/ObjectYAML/DXContainerYAMLTest.cpp
@@ -614,6 +614,35 @@ TEST(DXCFile, ParseILDNPart) {
EXPECT_TRUE(memcmp(Buffer, Storage.data(), 56u) == 0);
}
+TEST(DXCFile, ParsePRIVPart) {
+ SmallString<128> Storage;
+
+ ASSERT_TRUE(convert(Storage, R"(--- !dxcontainer
+Header:
+ Hash: [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ]
+ Version:
+ Major: 1
+ Minor: 0
+ PartCount: 1
+ PartOffsets: [ 36 ]
+Parts:
+ - Name: PRIV
+ Size: 5
+ PrivateData: [ 0xDE, 0xAD, 0xBE, 0xEF, 0x42 ]
+ )"));
+
+ uint8_t Buffer[] = {0x44, 0x58, 0x42, 0x43, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x31, 0x00, 0x00,
+ 0x00, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+ 0x50, 0x52, 0x49, 0x56, 0x05, 0x00, 0x00, 0x00, 0xDE,
+ 0xAD, 0xBE, 0xEF, 0x42};
+
+ EXPECT_EQ(Storage.size(), 49u);
+ EXPECT_TRUE(memcmp(Buffer, Storage.data(), 49u) == 0);
+}
+
TEST(DXCFile, ParseVERSPart) {
SmallString<128> Storage;
More information about the llvm-commits
mailing list