[llvm] 2bdcfc7 - [DirectX] Add `extract-section` to `llvm-objcopy` and implement it for `DXContainer` (#154804)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Sep 9 07:46:10 PDT 2025
Author: Finn Plummer
Date: 2025-09-09T08:37:12-06:00
New Revision: 2bdcfc7ab86b5cc8b95474e9571c9477f5df4451
URL: https://github.com/llvm/llvm-project/commit/2bdcfc7ab86b5cc8b95474e9571c9477f5df4451
DIFF: https://github.com/llvm/llvm-project/commit/2bdcfc7ab86b5cc8b95474e9571c9477f5df4451.diff
LOG: [DirectX] Add `extract-section` to `llvm-objcopy` and implement it for `DXContainer` (#154804)
This pr adds the `extract-section` option to `llvm-objcopy` as a common
option. It differs from `dump-section` as it will produce a standalone
object with just one section, as opposed to just the section contents.
For more context as to other options considered, see
https://github.com/llvm/llvm-project/pull/153265#issuecomment-3195696003.
This difference in behaviour is used for DXC compatibility with
`extract-rootsignature` and `/Frs`.
This pr then implements this functionality for `DXContainer` objects.
This is the second step of
https://github.com/llvm/llvm-project/issues/150277 to implement as a
compiler action that invokes `llvm-objcopy` for functionality.
This also completes the implementation of `extract-rootsignature` as
described in https://github.com/llvm/llvm-project/issues/149560.
Added:
llvm/test/tools/llvm-objcopy/DXContainer/extract-section-basic.test
llvm/test/tools/llvm-objcopy/DXContainer/extract-section-errs.test
llvm/test/tools/llvm-objcopy/DXContainer/extract-section-headers.test
Modified:
llvm/docs/CommandGuide/llvm-objcopy.rst
llvm/include/llvm/ObjCopy/CommonConfig.h
llvm/include/llvm/ObjCopy/ConfigManager.h
llvm/lib/ObjCopy/ConfigManager.cpp
llvm/lib/ObjCopy/DXContainer/DXContainerObjcopy.cpp
llvm/tools/llvm-objcopy/CommonOpts.td
llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
Removed:
################################################################################
diff --git a/llvm/docs/CommandGuide/llvm-objcopy.rst b/llvm/docs/CommandGuide/llvm-objcopy.rst
index 35d907fbe44d4..343e1d8dbac90 100644
--- a/llvm/docs/CommandGuide/llvm-objcopy.rst
+++ b/llvm/docs/CommandGuide/llvm-objcopy.rst
@@ -79,6 +79,15 @@ multiple file formats.
Enable deterministic mode when copying archives, i.e. use 0 for archive member
header UIDs, GIDs and timestamp fields. On by default.
+.. option:: --extract-section <section>=<file>
+
+ Extract the specified section ``<section>`` into the file ``<file>`` as a
+ seperate object. Can be specified multiple times to extract multiple sections.
+ ``<file>`` is unrelated to the input and output files provided to
+ :program:`llvm-objcopy` and as such the normal copying and editing
+ operations will still be performed. No operations are performed on the sections
+ prior to dumping them.
+
.. option:: --globalize-symbol <symbol>
Mark any defined symbols named ``<symbol>`` as global symbols in the output.
diff --git a/llvm/include/llvm/ObjCopy/CommonConfig.h b/llvm/include/llvm/ObjCopy/CommonConfig.h
index faa7b0db757a3..bad4bfdeaa46e 100644
--- a/llvm/include/llvm/ObjCopy/CommonConfig.h
+++ b/llvm/include/llvm/ObjCopy/CommonConfig.h
@@ -233,6 +233,7 @@ struct CommonConfig {
SmallVector<StringRef, 0> DumpSection;
SmallVector<NewSectionInfo, 0> UpdateSection;
SmallVector<SectionPatternAddressUpdate, 0> ChangeSectionAddress;
+ SmallVector<StringRef, 0> ExtractSection;
// Section matchers
NameMatcher KeepSection;
diff --git a/llvm/include/llvm/ObjCopy/ConfigManager.h b/llvm/include/llvm/ObjCopy/ConfigManager.h
index 4b596c604ea3a..27fbd96fc486c 100644
--- a/llvm/include/llvm/ObjCopy/ConfigManager.h
+++ b/llvm/include/llvm/ObjCopy/ConfigManager.h
@@ -27,7 +27,7 @@ struct LLVM_ABI ConfigManager : public MultiFormatConfig {
const CommonConfig &getCommonConfig() const override { return Common; }
- Expected<const ELFConfig &> getELFConfig() const override { return ELF; }
+ Expected<const ELFConfig &> getELFConfig() const override;
Expected<const COFFConfig &> getCOFFConfig() const override;
diff --git a/llvm/lib/ObjCopy/ConfigManager.cpp b/llvm/lib/ObjCopy/ConfigManager.cpp
index 24f3caccf7dc1..eef8a2190c4d2 100644
--- a/llvm/lib/ObjCopy/ConfigManager.cpp
+++ b/llvm/lib/ObjCopy/ConfigManager.cpp
@@ -13,6 +13,13 @@
using namespace llvm;
using namespace llvm::objcopy;
+Expected<const ELFConfig &> ConfigManager::getELFConfig() const {
+ if (!Common.ExtractSection.empty())
+ return createStringError(llvm::errc::invalid_argument,
+ "option is not supported for ELF");
+ return ELF;
+}
+
Expected<const COFFConfig &> ConfigManager::getCOFFConfig() const {
if (!Common.SplitDWO.empty() || !Common.SymbolsPrefix.empty() ||
!Common.SymbolsPrefixRemove.empty() || !Common.SymbolsToSkip.empty() ||
@@ -27,7 +34,7 @@ Expected<const COFFConfig &> ConfigManager::getCOFFConfig() const {
Common.DiscardMode == DiscardType::Locals ||
!Common.SymbolsToAdd.empty() || Common.GapFill != 0 ||
Common.PadTo != 0 || Common.ChangeSectionLMAValAll != 0 ||
- !Common.ChangeSectionAddress.empty())
+ !Common.ChangeSectionAddress.empty() || !Common.ExtractSection.empty())
return createStringError(llvm::errc::invalid_argument,
"option is not supported for COFF");
@@ -48,7 +55,7 @@ Expected<const MachOConfig &> ConfigManager::getMachOConfig() const {
Common.DiscardMode == DiscardType::Locals ||
!Common.SymbolsToAdd.empty() || Common.GapFill != 0 ||
Common.PadTo != 0 || Common.ChangeSectionLMAValAll != 0 ||
- !Common.ChangeSectionAddress.empty())
+ !Common.ChangeSectionAddress.empty() || !Common.ExtractSection.empty())
return createStringError(llvm::errc::invalid_argument,
"option is not supported for MachO");
@@ -69,7 +76,7 @@ Expected<const WasmConfig &> ConfigManager::getWasmConfig() const {
!Common.SetSectionFlags.empty() || !Common.SetSectionType.empty() ||
!Common.SymbolsToRename.empty() || Common.GapFill != 0 ||
Common.PadTo != 0 || Common.ChangeSectionLMAValAll != 0 ||
- !Common.ChangeSectionAddress.empty())
+ !Common.ChangeSectionAddress.empty() || !Common.ExtractSection.empty())
return createStringError(llvm::errc::invalid_argument,
"only flags for section dumping, removal, and "
"addition are supported");
@@ -99,7 +106,7 @@ Expected<const XCOFFConfig &> ConfigManager::getXCOFFConfig() const {
Common.Weaken || Common.StripUnneeded || Common.DecompressDebugSections ||
Common.GapFill != 0 || Common.PadTo != 0 ||
Common.ChangeSectionLMAValAll != 0 ||
- !Common.ChangeSectionAddress.empty()) {
+ !Common.ChangeSectionAddress.empty() || !Common.ExtractSection.empty()) {
return createStringError(
llvm::errc::invalid_argument,
"no flags are supported yet, only basic copying is allowed");
@@ -124,9 +131,8 @@ ConfigManager::getDXContainerConfig() const {
Common.DecompressDebugSections || Common.GapFill != 0 ||
Common.PadTo != 0 || Common.ChangeSectionLMAValAll != 0 ||
!Common.ChangeSectionAddress.empty()) {
- return createStringError(
- llvm::errc::invalid_argument,
- "no flags are supported yet, only basic copying is allowed");
+ return createStringError(llvm::errc::invalid_argument,
+ "option is not supported for DXContainer");
}
return DXContainer;
}
diff --git a/llvm/lib/ObjCopy/DXContainer/DXContainerObjcopy.cpp b/llvm/lib/ObjCopy/DXContainer/DXContainerObjcopy.cpp
index ac97dc462c142..d7f3c0d1f7b36 100644
--- a/llvm/lib/ObjCopy/DXContainer/DXContainerObjcopy.cpp
+++ b/llvm/lib/ObjCopy/DXContainer/DXContainerObjcopy.cpp
@@ -11,6 +11,7 @@
#include "DXContainerWriter.h"
#include "llvm/ObjCopy/CommonConfig.h"
#include "llvm/ObjCopy/DXContainer/DXContainerConfig.h"
+#include "llvm/Support/raw_ostream.h"
namespace llvm {
namespace objcopy {
@@ -18,7 +19,40 @@ namespace dxbc {
using namespace object;
+static Error extractPartAsObject(StringRef PartName, StringRef OutFilename,
+ StringRef InputFilename, const Object &Obj) {
+ for (const Part &P : Obj.Parts)
+ if (P.Name == PartName) {
+ Object PartObj;
+ PartObj.Header = Obj.Header;
+ PartObj.Parts.push_back({P.Name, P.Data});
+ PartObj.recomputeHeader();
+
+ auto Write = [&OutFilename, &PartObj](raw_ostream &Out) -> Error {
+ DXContainerWriter Writer(PartObj, Out);
+ if (Error E = Writer.write())
+ return createFileError(OutFilename, std::move(E));
+ return Error::success();
+ };
+
+ return writeToOutput(OutFilename, Write);
+ }
+
+ return createFileError(InputFilename, object_error::parse_failed,
+ "part '%s' not found", PartName.str().c_str());
+}
+
static Error handleArgs(const CommonConfig &Config, Object &Obj) {
+ // Extract all sections before any modifications.
+ for (StringRef Flag : Config.ExtractSection) {
+ StringRef SectionName;
+ StringRef FileName;
+ std::tie(SectionName, FileName) = Flag.split('=');
+ if (Error E = extractPartAsObject(SectionName, FileName,
+ Config.InputFilename, Obj))
+ return E;
+ }
+
std::function<bool(const Part &)> RemovePred = [](const Part &) {
return false;
};
diff --git a/llvm/test/tools/llvm-objcopy/DXContainer/extract-section-basic.test b/llvm/test/tools/llvm-objcopy/DXContainer/extract-section-basic.test
new file mode 100644
index 0000000000000..fc16e51e8b78e
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/DXContainer/extract-section-basic.test
@@ -0,0 +1,111 @@
+## Tests that a separate DXContainer is created for the RTS0 (root signature)
+## part, when--extract-section is specified.
+
+# RUN: yaml2obj %s -o %t
+# RUN: llvm-objcopy %t --extract-section=RTS0=%t.rts0.out
+# RUN: obj2yaml %t.rts0.out | FileCheck %s --implicit-check-not=Name:
+
+## The DXContainer described below was generated with:
+
+## `clang-dxc -T cs_6_7 test.hlsl /Fo temp.dxo`
+## `obj2yaml temp.dxo`
+
+## and has the DXIL section trimmed for readability.
+
+## ``` test.hlsl
+## [RootSignature("")]
+## [numthreads(1,1,1)]
+## void main() {}
+## ```
+
+# CHECK: Header:
+# CHECK-NEXT: Hash:
+# CHECK: Version:
+# CHECK-NEXT: Major: 1
+# CHECK-NEXT: Minor: 0
+# CHECK-NEXT: FileSize: 68
+# CHECK-NEXT: PartCount: 1
+# CHECK-NEXT: PartOffsets: [ 36 ]
+# CHECK-NEXT: Parts:
+# CHECK-NEXT: Name: RTS0
+# CHECK-NEXT Size: 24
+# CHECK-NEXT RootSignature:
+# CHECK-NEXT Version: 2
+# CHECK-NEXT NumRootParameters: 0
+# CHECK-NEXT RootParametersOffset: 24
+# CHECK-NEXT NumStaticSamplers: 0
+# CHECK-NEXT StaticSamplersOffset: 24
+# CHECK-NEXT Parameters: []
+
+--- !dxcontainer
+Header:
+ Hash: [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ]
+ Version:
+ Major: 1
+ Minor: 0
+ FileSize: 1984
+ PartCount: 7
+ PartOffsets: [ 60, 1792, 1808, 1836, 1852, 1868, 1900 ]
+Parts:
+ - Name: DXIL
+ Size: 1724
+ Program:
+ MajorVersion: 6
+ MinorVersion: 7
+ ShaderKind: 5
+ Size: 28
+ DXILMajorVersion: 1
+ DXILMinorVersion: 7
+ DXILSize: 4
+ DXIL: [ 0x42, 0x43, 0xC0, 0xDE, ]
+ - Name: SFI0
+ Size: 8
+ - Name: HASH
+ Size: 20
+ Hash:
+ IncludesSource: false
+ Digest: [ 0x9F, 0xD1, 0xD9, 0xE2, 0x49, 0xFB, 0x3A, 0x6C,
+ 0x8C, 0x14, 0x8A, 0x96, 0x1C, 0x7D, 0x85, 0xA9 ]
+ - Name: ISG1
+ Size: 8
+ Signature:
+ Parameters: []
+ - Name: OSG1
+ Size: 8
+ Signature:
+ Parameters: []
+ - Name: RTS0
+ Size: 24
+ RootSignature:
+ Version: 2
+ NumRootParameters: 0
+ RootParametersOffset: 24
+ NumStaticSamplers: 0
+ StaticSamplersOffset: 24
+ Parameters: []
+ - Name: PSV0
+ Size: 76
+ PSVInfo:
+ Version: 3
+ ShaderStage: 5
+ MinimumWaveLaneCount: 0
+ MaximumWaveLaneCount: 4294967295
+ UsesViewID: 0
+ SigInputVectors: 0
+ SigOutputVectors: [ 0, 0, 0, 0 ]
+ NumThreadsX: 1
+ NumThreadsY: 1
+ NumThreadsZ: 1
+ EntryName: main
+ ResourceStride: 24
+ Resources: []
+ SigInputElements: []
+ SigOutputElements: []
+ SigPatchOrPrimElements: []
+ InputOutputMap:
+ - [ ]
+ - [ ]
+ - [ ]
+ - [ ]
+...
diff --git a/llvm/test/tools/llvm-objcopy/DXContainer/extract-section-errs.test b/llvm/test/tools/llvm-objcopy/DXContainer/extract-section-errs.test
new file mode 100644
index 0000000000000..2156b62cb2b7c
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/DXContainer/extract-section-errs.test
@@ -0,0 +1,40 @@
+## Check that llvm-objcopy reports a suitable error when it can't find the
+## section to extract.
+
+## We can't extract a part that doesn't exist.
+# RUN: yaml2obj %s --docnum=1 -o %t1
+# RUN: not llvm-objcopy %t1 --extract-section=UNKNOWN=%t.unknown.out 2>&1 | FileCheck %s -DFILE=%t1 --check-prefix=ERROR1
+
+# ERROR1: error: '[[FILE]]': part 'UNKNOWN' not found
+
+--- !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: FKE0
+ Size: 8
+...
+
+## We can't extract a part that is specified incorrectly.
+# RUN: yaml2obj %s --docnum=2 -o %t2
+# RUN: not llvm-objcopy %t2 --extract-section=FKE0,%t.fke0.out 2>&1 | FileCheck %s -DFILE=%t2 --check-prefix=ERROR2
+
+# ERROR2: error: bad format for --extract-section, expected section=file
+
+--- !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: FKE0
+ Size: 8
+...
diff --git a/llvm/test/tools/llvm-objcopy/DXContainer/extract-section-headers.test b/llvm/test/tools/llvm-objcopy/DXContainer/extract-section-headers.test
new file mode 100644
index 0000000000000..53818178b95cb
--- /dev/null
+++ b/llvm/test/tools/llvm-objcopy/DXContainer/extract-section-headers.test
@@ -0,0 +1,53 @@
+## Tests that a separate DXContainer is created with only the specified section
+## for each --extract-section specified.
+
+# RUN: yaml2obj %s -o %t
+# RUN: llvm-objcopy %t --extract-section=FKE1=%t.fke1.out --extract-section=FKE4=%t.fke4.out
+# RUN: obj2yaml %t.fke1.out | FileCheck %s --check-prefixes=CHECK,FKE1 --implicit-check-not=Name:
+# RUN: obj2yaml %t.fke4.out | FileCheck %s --check-prefixes=CHECK,FKE4 --implicit-check-not=Name:
+
+# FKE1: FileSize: 52
+# FKE4: FileSize: 1732
+# CHECK-NEXT: PartCount: 1
+# CHECK-NEXT: PartOffsets: [ 36 ]
+# CHECK-NEXT: Parts:
+# FKE1-NEXT: Name: FKE1
+# FKE1-NEXT: Size: 8
+# FKE4-NEXT: Name: FKE4
+# FKE4-NEXT: Size: 1688
+
+--- !dxcontainer
+Header:
+ Hash: [ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+ 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 ]
+ Version:
+ Major: 1
+ Minor: 0
+ FileSize: 1996
+ PartCount: 7
+ PartOffsets: [ 60, 76, 92, 108, 236, 1932, 1960 ]
+Parts:
+ - Name: FKE0
+ Size: 8
+ - Name: FKE1
+ Size: 8
+ - Name: FKE2
+ Size: 8
+ - Name: FKE3
+ Size: 120
+ - Name: FKE4
+ Size: 1688
+ - Name: FKE5
+ Size: 20
+ - Name: DXIL
+ Size: 28
+ Program:
+ MajorVersion: 6
+ MinorVersion: 5
+ ShaderKind: 5
+ Size: 8
+ DXILMajorVersion: 1
+ DXILMinorVersion: 5
+ DXILSize: 4
+ DXIL: [ 0x42, 0x43, 0xC0, 0xDE, ]
+...
diff --git a/llvm/tools/llvm-objcopy/CommonOpts.td b/llvm/tools/llvm-objcopy/CommonOpts.td
index c247c93f6e0f2..7359e628582d2 100644
--- a/llvm/tools/llvm-objcopy/CommonOpts.td
+++ b/llvm/tools/llvm-objcopy/CommonOpts.td
@@ -23,6 +23,12 @@ def enable_deterministic_archives
: Flag<["--"], "enable-deterministic-archives">,
HelpText<"Enable deterministic mode when operating on archives (use "
"zero for UIDs, GIDs, and timestamps).">;
+
+defm extract_section
+ : Eq<"extract-section",
+ "Extract section named <section> into standalone object in file <file>">,
+ MetaVarName<"section=file">;
+
def D : Flag<["-"], "D">,
Alias<enable_deterministic_archives>,
HelpText<"Alias for --enable-deterministic-archives">;
diff --git a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
index 175f77c894825..3d7f33cd64bf4 100644
--- a/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
+++ b/llvm/tools/llvm-objcopy/ObjcopyOptions.cpp
@@ -1082,6 +1082,14 @@ objcopy::parseObjcopyOptions(ArrayRef<const char *> ArgsArr,
"bad format for --dump-section, expected section=file");
Config.DumpSection.push_back(Value);
}
+ for (auto *Arg : InputArgs.filtered(OBJCOPY_extract_section)) {
+ StringRef Value(Arg->getValue());
+ if (Value.split('=').second.empty())
+ return createStringError(
+ errc::invalid_argument,
+ "bad format for --extract-section, expected section=file");
+ Config.ExtractSection.push_back(Value);
+ }
Config.StripAll = InputArgs.hasArg(OBJCOPY_strip_all);
Config.StripAllGNU = InputArgs.hasArg(OBJCOPY_strip_all_gnu);
Config.StripDebug = InputArgs.hasArg(OBJCOPY_strip_debug);
More information about the llvm-commits
mailing list