[lld] [lld][COFF] Add /discard-section option to discard input sections by name (PR #189542)

Haohai Wen via llvm-commits llvm-commits at lists.llvm.org
Thu Apr 2 03:25:15 PDT 2026


https://github.com/HaohaiWen updated https://github.com/llvm/llvm-project/pull/189542

>From eb61f449735d48188ce079d14e001ea032e54f3c Mon Sep 17 00:00:00 2001
From: Haohai Wen <haohai.wen at intel.com>
Date: Tue, 31 Mar 2026 13:53:54 +0800
Subject: [PATCH 1/3] [lld][COFF] Add -strip-embedded-bitcode option

Add a -strip-embedded-bitcode option so that users who compile with
-fembed-bitcode but don't want these sections in the final binary can
explicitly opt out, while preserving the default behavior.

The .llvmbc and .llvmcmd sections was previously stripped from the final
binary unconditionally (#150897). However, this broke the workflow of
-lto-embed-bitcode and #188398 reverted it.

The test of this PR is from #150897.
---
 lld/COFF/Config.h                |  1 +
 lld/COFF/Driver.cpp              |  3 +++
 lld/COFF/InputFiles.cpp          |  4 ++++
 lld/COFF/Options.td              |  2 ++
 lld/test/COFF/embed-bitcode.test | 39 ++++++++++++++++++++++++++++++++
 5 files changed, 49 insertions(+)
 create mode 100644 lld/test/COFF/embed-bitcode.test

diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h
index 1c0f874ddfd79..c15e11444092e 100644
--- a/lld/COFF/Config.h
+++ b/lld/COFF/Config.h
@@ -134,6 +134,7 @@ struct Configuration {
   bool forceUnresolved = false;
   bool debug = false;
   bool includeDwarfChunks = false;
+  bool stripEmbeddedBitcode = false;
   bool debugGHashes = false;
   bool writeSymtab = false;
   bool driver = false;
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index df76f05ed5a06..9416f1c1147bc 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2129,6 +2129,9 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   if (auto *arg = args.getLastArg(OPT_sectionlayout))
     parseSectionLayout(arg->getValue());
 
+  // Handle /strip-embedded-bitcode
+  config->stripEmbeddedBitcode = args.hasArg(OPT_strip_embedded_bitcode);
+
   // Handle /align
   if (auto *arg = args.getLastArg(OPT_align)) {
     parseNumbers(arg->getValue(), &config->align);
diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp
index 0cc3aaeba41e3..3f9eac77f1fc0 100644
--- a/lld/COFF/InputFiles.cpp
+++ b/lld/COFF/InputFiles.cpp
@@ -403,6 +403,10 @@ SectionChunk *ObjFile::readSection(uint32_t sectionNumber,
     return nullptr;
   }
 
+  if (symtab.ctx.config.stripEmbeddedBitcode &&
+      (name == ".llvmbc" || name == ".llvmcmd"))
+    return nullptr;
+
   // Object files may have DWARF debug info or MS CodeView debug info
   // (or both).
   //
diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td
index fb762b880c2cb..a2f08726d6db9 100644
--- a/lld/COFF/Options.td
+++ b/lld/COFF/Options.td
@@ -110,6 +110,8 @@ def pdbstream : Joined<["/", "-", "/?", "-?"], "pdbstream:">,
 def section : P<"section", "Specify section attributes">;
 def sectionlayout : P<"sectionlayout", "Specifies the layout strategy for output sections">;
 def stack   : P<"stack", "Size of the stack">;
+def strip_embedded_bitcode : F<"strip-embedded-bitcode">,
+    HelpText<"Strip .llvmbc and .llvmcmd sections from the output">;
 def stub    : P<"stub", "Specify DOS stub file">;
 def subsystem : P<"subsystem", "Specify subsystem">;
 def timestamp : P<"timestamp", "Specify the PE header timestamp">;
diff --git a/lld/test/COFF/embed-bitcode.test b/lld/test/COFF/embed-bitcode.test
new file mode 100644
index 0000000000000..d452c35fd9f82
--- /dev/null
+++ b/lld/test/COFF/embed-bitcode.test
@@ -0,0 +1,39 @@
+# RUN: yaml2obj %s -o %t.obj
+
+## By default, .llvmbc and .llvmcmd sections are preserved.
+# RUN: lld-link /entry:main /subsystem:console /out:%t.exe %t.obj
+# RUN: llvm-readobj -S %t.exe | FileCheck --check-prefix=PRESERVE %s
+
+# PRESERVE: Name: .llvmbc
+# PRESERVE: Name: .llvmcmd
+
+## -strip-embedded-bitcode strips .llvmbc and .llvmcmd sections.
+# RUN: lld-link /entry:main /subsystem:console /strip-embedded-bitcode /out:%t-stripped.exe %t.obj
+# RUN: llvm-readobj -S %t-stripped.exe | FileCheck --check-prefix=STRIP %s
+
+# STRIP-NOT: Name: .llvmbc
+# STRIP-NOT: Name: .llvmcmd
+
+--- !COFF
+header:
+  Machine:         IMAGE_FILE_MACHINE_AMD64
+
+sections:
+  - Name:            .text
+    Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+    SectionData:     "C3"
+  - Name:           .llvmbc
+    Characteristics: [ IMAGE_SCN_MEM_DISCARDABLE ]
+    SectionData:     "4243C0DE"
+  - Name:           .llvmcmd
+    Characteristics: [ IMAGE_SCN_MEM_DISCARDABLE ]
+    SectionData:     "2D63633100"
+
+symbols:
+  - Name:            main
+    Value:           0
+    SectionNumber:   1
+    SimpleType:      IMAGE_SYM_TYPE_NULL
+    ComplexType:     IMAGE_SYM_DTYPE_FUNCTION
+    StorageClass:    IMAGE_SYM_CLASS_EXTERNAL
+...

>From df502cc1e4e49a73e7709a79bb6cc8cd4125c505 Mon Sep 17 00:00:00 2001
From: Haohai Wen <haohai.wen at intel.com>
Date: Tue, 31 Mar 2026 13:53:54 +0800
Subject: [PATCH 2/3] [lld][COFF] Add /discard option to discard input sections
 by name

This provides a general mechanism similar to ELF linker scripts'
/DISCARD/ for COFF. Though the intention is to explicitly discard
.llvmbc and .llvmcmd sections. (See discussion in #150897, #188398
for more details.)
---
 lld/COFF/Config.h                             |  4 +++-
 lld/COFF/Driver.cpp                           |  7 +++---
 lld/COFF/InputFiles.cpp                       |  3 +--
 lld/COFF/Options.td                           |  3 +--
 .../COFF/{embed-bitcode.test => discard.test} | 24 ++++++++++++++-----
 5 files changed, 27 insertions(+), 14 deletions(-)
 rename lld/test/COFF/{embed-bitcode.test => discard.test} (57%)

diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h
index c15e11444092e..a75bb278b59e9 100644
--- a/lld/COFF/Config.h
+++ b/lld/COFF/Config.h
@@ -134,7 +134,6 @@ struct Configuration {
   bool forceUnresolved = false;
   bool debug = false;
   bool includeDwarfChunks = false;
-  bool stripEmbeddedBitcode = false;
   bool debugGHashes = false;
   bool writeSymtab = false;
   bool driver = false;
@@ -217,6 +216,9 @@ struct Configuration {
   // Used for /merge:from=to (e.g. /merge:.rdata=.text)
   std::map<StringRef, StringRef> merge;
 
+  // Used for /discard:.name
+  llvm::StringSet<> discard;
+
   // Used for /section=.name,{DEKPRSW} to set section attributes.
   std::map<StringRef, uint32_t> section;
   // Used for /sectionlayout: to layout sections in specified order.
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 9416f1c1147bc..43b5bac6f03fc 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2103,6 +2103,10 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   for (auto *arg : args.filtered(OPT_merge))
     parseMerge(arg->getValue());
 
+  // Handle /discard
+  for (auto *arg : args.filtered(OPT_discard))
+    config->discard.insert(arg->getValue());
+
   // Add default section merging rules after user rules. User rules take
   // precedence, but we will emit a warning if there is a conflict.
   parseMerge(".idata=.rdata");
@@ -2129,9 +2133,6 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   if (auto *arg = args.getLastArg(OPT_sectionlayout))
     parseSectionLayout(arg->getValue());
 
-  // Handle /strip-embedded-bitcode
-  config->stripEmbeddedBitcode = args.hasArg(OPT_strip_embedded_bitcode);
-
   // Handle /align
   if (auto *arg = args.getLastArg(OPT_align)) {
     parseNumbers(arg->getValue(), &config->align);
diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp
index 3f9eac77f1fc0..17a026efc5c7c 100644
--- a/lld/COFF/InputFiles.cpp
+++ b/lld/COFF/InputFiles.cpp
@@ -403,8 +403,7 @@ SectionChunk *ObjFile::readSection(uint32_t sectionNumber,
     return nullptr;
   }
 
-  if (symtab.ctx.config.stripEmbeddedBitcode &&
-      (name == ".llvmbc" || name == ".llvmcmd"))
+  if (symtab.ctx.config.discard.contains(name))
     return nullptr;
 
   // Object files may have DWARF debug info or MS CodeView debug info
diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td
index a2f08726d6db9..8633756c3680c 100644
--- a/lld/COFF/Options.td
+++ b/lld/COFF/Options.td
@@ -45,6 +45,7 @@ def color_diagnostics_eq: Joined<["--"], "color-diagnostics=">,
 def defaultlib : P<"defaultlib", "Add the library to the list of input files">;
 def delayload : P<"delayload", "Delay loaded DLL name">;
 def diasdkdir : P<"diasdkdir", "Set the location of the DIA SDK">;
+def discard : P<"discard", "Discard input sections by name">;
 def dwodir : P<"dwodir",
     "Directory to store .dwo files when LTO and debug fission are used">;
 def entry   : P<"entry", "Name of entry point symbol">;
@@ -110,8 +111,6 @@ def pdbstream : Joined<["/", "-", "/?", "-?"], "pdbstream:">,
 def section : P<"section", "Specify section attributes">;
 def sectionlayout : P<"sectionlayout", "Specifies the layout strategy for output sections">;
 def stack   : P<"stack", "Size of the stack">;
-def strip_embedded_bitcode : F<"strip-embedded-bitcode">,
-    HelpText<"Strip .llvmbc and .llvmcmd sections from the output">;
 def stub    : P<"stub", "Specify DOS stub file">;
 def subsystem : P<"subsystem", "Specify subsystem">;
 def timestamp : P<"timestamp", "Specify the PE header timestamp">;
diff --git a/lld/test/COFF/embed-bitcode.test b/lld/test/COFF/discard.test
similarity index 57%
rename from lld/test/COFF/embed-bitcode.test
rename to lld/test/COFF/discard.test
index d452c35fd9f82..1b8514325486d 100644
--- a/lld/test/COFF/embed-bitcode.test
+++ b/lld/test/COFF/discard.test
@@ -1,18 +1,27 @@
 # RUN: yaml2obj %s -o %t.obj
 
-## By default, .llvmbc and .llvmcmd sections are preserved.
+## By default, all sections are preserved.
 # RUN: lld-link /entry:main /subsystem:console /out:%t.exe %t.obj
 # RUN: llvm-readobj -S %t.exe | FileCheck --check-prefix=PRESERVE %s
 
+# PRESERVE: Name: .rdata
 # PRESERVE: Name: .llvmbc
 # PRESERVE: Name: .llvmcmd
 
-## -strip-embedded-bitcode strips .llvmbc and .llvmcmd sections.
-# RUN: lld-link /entry:main /subsystem:console /strip-embedded-bitcode /out:%t-stripped.exe %t.obj
-# RUN: llvm-readobj -S %t-stripped.exe | FileCheck --check-prefix=STRIP %s
+## /discard discards sections by name.
+# RUN: lld-link /entry:main /subsystem:console /discard:.llvmbc /discard:.llvmcmd /out:%t-stripped.exe %t.obj
+# RUN: llvm-readobj -S %t-stripped.exe | FileCheck --check-prefix=NOBC %s
 
-# STRIP-NOT: Name: .llvmbc
-# STRIP-NOT: Name: .llvmcmd
+# NOBC:     Name: .rdata
+# NOBC-NOT: Name: .llvmbc
+# NOBC-NOT: Name: .llvmcmd
+
+# RUN: lld-link /entry:main /subsystem:console /discard:.rdata /out:%t-nordata.exe %t.obj
+# RUN: llvm-readobj -S %t-nordata.exe | FileCheck --check-prefix=NORDATA %s
+
+# NORDATA-NOT: Name: .rdata
+# NORDATA:     Name: .llvmbc
+# NORDATA:     Name: .llvmcmd
 
 --- !COFF
 header:
@@ -22,6 +31,9 @@ sections:
   - Name:            .text
     Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
     SectionData:     "C3"
+  - Name:            .rdata
+    Characteristics: [ IMAGE_SCN_CNT_INITIALIZED_DATA, IMAGE_SCN_MEM_READ ]
+    SectionData:     "00000000"
   - Name:           .llvmbc
     Characteristics: [ IMAGE_SCN_MEM_DISCARDABLE ]
     SectionData:     "4243C0DE"

>From 03ffc947d40d13026840515a6a4936b56b8c63d6 Mon Sep 17 00:00:00 2001
From: Haohai Wen <haohai.wen at intel.com>
Date: Thu, 2 Apr 2026 17:49:51 +0800
Subject: [PATCH 3/3] [lld][COFF] Rename /discard to /discard-section

Rename per reviewer suggestion. GNU ld has --discard-all and
--discard-locals, so /discard-section avoids ambiguity and follows
the lld extension dash naming convention.
---
 lld/COFF/Config.h                                    | 4 ++--
 lld/COFF/Driver.cpp                                  | 6 +++---
 lld/COFF/InputFiles.cpp                              | 2 +-
 lld/COFF/Options.td                                  | 2 +-
 lld/test/COFF/{discard.test => discard-section.test} | 6 +++---
 5 files changed, 10 insertions(+), 10 deletions(-)
 rename lld/test/COFF/{discard.test => discard-section.test} (83%)

diff --git a/lld/COFF/Config.h b/lld/COFF/Config.h
index a75bb278b59e9..6ac6355d976c2 100644
--- a/lld/COFF/Config.h
+++ b/lld/COFF/Config.h
@@ -216,8 +216,8 @@ struct Configuration {
   // Used for /merge:from=to (e.g. /merge:.rdata=.text)
   std::map<StringRef, StringRef> merge;
 
-  // Used for /discard:.name
-  llvm::StringSet<> discard;
+  // Used for /discard-section:.name
+  llvm::StringSet<> discardSection;
 
   // Used for /section=.name,{DEKPRSW} to set section attributes.
   std::map<StringRef, uint32_t> section;
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 43b5bac6f03fc..23aa756cf6ae8 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2103,9 +2103,9 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   for (auto *arg : args.filtered(OPT_merge))
     parseMerge(arg->getValue());
 
-  // Handle /discard
-  for (auto *arg : args.filtered(OPT_discard))
-    config->discard.insert(arg->getValue());
+  // Handle /discard-section
+  for (auto *arg : args.filtered(OPT_discard_section))
+    config->discardSection.insert(arg->getValue());
 
   // Add default section merging rules after user rules. User rules take
   // precedence, but we will emit a warning if there is a conflict.
diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp
index 17a026efc5c7c..9040d9653d2b2 100644
--- a/lld/COFF/InputFiles.cpp
+++ b/lld/COFF/InputFiles.cpp
@@ -403,7 +403,7 @@ SectionChunk *ObjFile::readSection(uint32_t sectionNumber,
     return nullptr;
   }
 
-  if (symtab.ctx.config.discard.contains(name))
+  if (symtab.ctx.config.discardSection.contains(name))
     return nullptr;
 
   // Object files may have DWARF debug info or MS CodeView debug info
diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td
index 8633756c3680c..bc1902d093a0f 100644
--- a/lld/COFF/Options.td
+++ b/lld/COFF/Options.td
@@ -45,7 +45,7 @@ def color_diagnostics_eq: Joined<["--"], "color-diagnostics=">,
 def defaultlib : P<"defaultlib", "Add the library to the list of input files">;
 def delayload : P<"delayload", "Delay loaded DLL name">;
 def diasdkdir : P<"diasdkdir", "Set the location of the DIA SDK">;
-def discard : P<"discard", "Discard input sections by name">;
+def discard_section : P<"discard-section", "Discard input sections by name">;
 def dwodir : P<"dwodir",
     "Directory to store .dwo files when LTO and debug fission are used">;
 def entry   : P<"entry", "Name of entry point symbol">;
diff --git a/lld/test/COFF/discard.test b/lld/test/COFF/discard-section.test
similarity index 83%
rename from lld/test/COFF/discard.test
rename to lld/test/COFF/discard-section.test
index 1b8514325486d..89197d9fbb854 100644
--- a/lld/test/COFF/discard.test
+++ b/lld/test/COFF/discard-section.test
@@ -8,15 +8,15 @@
 # PRESERVE: Name: .llvmbc
 # PRESERVE: Name: .llvmcmd
 
-## /discard discards sections by name.
-# RUN: lld-link /entry:main /subsystem:console /discard:.llvmbc /discard:.llvmcmd /out:%t-stripped.exe %t.obj
+## /discard-section discards sections by name.
+# RUN: lld-link /entry:main /subsystem:console /discard-section:.llvmbc /discard-section:.llvmcmd /out:%t-stripped.exe %t.obj
 # RUN: llvm-readobj -S %t-stripped.exe | FileCheck --check-prefix=NOBC %s
 
 # NOBC:     Name: .rdata
 # NOBC-NOT: Name: .llvmbc
 # NOBC-NOT: Name: .llvmcmd
 
-# RUN: lld-link /entry:main /subsystem:console /discard:.rdata /out:%t-nordata.exe %t.obj
+# RUN: lld-link /entry:main /subsystem:console /discard-section:.rdata /out:%t-nordata.exe %t.obj
 # RUN: llvm-readobj -S %t-nordata.exe | FileCheck --check-prefix=NORDATA %s
 
 # NORDATA-NOT: Name: .rdata



More information about the llvm-commits mailing list