[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