[lld] [codex] [lld][COFF] Emit ThinLTO indexes for unused lazy bitcode (PR #203321)

David Zbarsky via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 11 09:20:49 PDT 2026


https://github.com/dzbarsky updated https://github.com/llvm/llvm-project/pull/203321

>From 8c17ea518d40b3583ff4188a9e52ec8b4fbdc71f Mon Sep 17 00:00:00 2001
From: David Zbarsky <dzbarsky at gmail.com>
Date: Thu, 11 Jun 2026 12:09:19 -0400
Subject: [PATCH] [lld][COFF] Emit ThinLTO indexes for unused lazy bitcode

Distributed ThinLTO build systems determine their expected backend inputs
before the linker resolves /start-lib members. The COFF linker only asked
the LTO API to write index and imports files for selected bitcode inputs.
An unused BitcodeFile left lazy by /start-lib therefore produced neither
file, causing build systems that declare those outputs to fail.

Track lazy BitcodeFile instances and, before moving selected inputs into
the LTO API, emit a ModuleSummaryIndex with
SkipModuleByDistributedBackend and an empty imports file for each input
that remains lazy. This matches the existing ELF, Mach-O, and WebAssembly
behavior and preserves /start-lib archive selection semantics.

Extend thinlto-emit-imports.ll to select one lazy module, leave another
lazy, and verify that the unused module's index tells the distributed
backend to skip it.
---
 lld/COFF/COFFLinkerContext.h          |  1 +
 lld/COFF/Driver.cpp                   |  9 +++++++--
 lld/COFF/LTO.cpp                      | 27 +++++++++++++++++++++++++++
 lld/COFF/LTO.h                        |  2 ++
 lld/test/COFF/thinlto-emit-imports.ll |  6 ++++--
 5 files changed, 41 insertions(+), 4 deletions(-)

diff --git a/lld/COFF/COFFLinkerContext.h b/lld/COFF/COFFLinkerContext.h
index 0f8f2062b9f2e..a2273e0ad9ae3 100644
--- a/lld/COFF/COFFLinkerContext.h
+++ b/lld/COFF/COFFLinkerContext.h
@@ -60,6 +60,7 @@ class COFFLinkerContext : public CommonLinkerContext {
   }
 
   std::vector<ObjFile *> objFileInstances;
+  std::vector<BitcodeFile *> lazyBitcodeFileInstances;
   std::map<std::string, PDBInputFile *> pdbInputFileInstances;
   std::vector<ImportFile *> importFileInstances;
   std::int64_t consumedInputsSize = 0;
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 024cb2c95cd20..e797471a3d61c 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -12,6 +12,7 @@
 #include "DebugTypes.h"
 #include "ICF.h"
 #include "InputFiles.h"
+#include "LTO.h"
 #include "MarkLive.h"
 #include "MinGW.h"
 #include "SymbolTable.h"
@@ -206,10 +207,12 @@ static bool compatibleMachineType(COFFLinkerContext &ctx, MachineTypes mt) {
 void LinkerDriver::addFile(InputFile *file) {
   Log(ctx) << "Reading " << toString(file);
   if (file->lazy) {
-    if (auto *f = dyn_cast<BitcodeFile>(file))
+    if (auto *f = dyn_cast<BitcodeFile>(file)) {
+      ctx.lazyBitcodeFileInstances.push_back(f);
       f->parseLazy();
-    else
+    } else {
       cast<ObjFile>(file)->parseLazy();
+    }
   } else {
     ctx.consumedInputsSize += file->mb.getBufferSize();
     file->parse();
@@ -2770,6 +2773,8 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   // Do LTO by compiling bitcode input files to a set of native COFF files then
   // link those files (unless -thinlto-index-only was given, in which case we
   // resolve symbols and write indices, but don't generate native code or link).
+  if (config->thinLTOIndexOnly)
+    thinLTOCreateEmptyIndexFiles(ctx);
   ltoCompilationDone = true;
   ctx.forEachSymtab([](SymbolTable &symtab) { symtab.compileBitcodeFiles(); });
 
diff --git a/lld/COFF/LTO.cpp b/lld/COFF/LTO.cpp
index 0329f6c2e9cea..ae706fa6d9930 100644
--- a/lld/COFF/LTO.cpp
+++ b/lld/COFF/LTO.cpp
@@ -156,6 +156,33 @@ BitcodeCompiler::BitcodeCompiler(COFFLinkerContext &c) : ctx(c) {
 
 BitcodeCompiler::~BitcodeCompiler() = default;
 
+// If a lazy BitcodeFile has not been added to the link, emit an empty index
+// file. This matches ELF and GNU gold plugin behavior for distributed builds.
+void lld::coff::thinLTOCreateEmptyIndexFiles(COFFLinkerContext &ctx) {
+  DenseSet<StringRef> linkedBitcodeFiles;
+  ctx.forEachSymtab([&](SymbolTable &symtab) {
+    for (BitcodeFile *f : symtab.bitcodeFileInstances)
+      linkedBitcodeFiles.insert(f->getName());
+  });
+
+  for (BitcodeFile *f : ctx.lazyBitcodeFileInstances) {
+    if (!f->lazy || linkedBitcodeFiles.contains(f->getName()))
+      continue;
+    std::string path = lto::getThinLTOOutputFile(
+        f->obj->getName(), ctx.config.thinLTOPrefixReplaceOld,
+        ctx.config.thinLTOPrefixReplaceNew);
+    std::unique_ptr<raw_fd_ostream> os = openFile(path + ".thinlto.bc");
+    if (!os)
+      continue;
+
+    ModuleSummaryIndex m(/*HaveGVs=*/false);
+    m.setSkipModuleByDistributedBackend();
+    writeIndexToFile(m, *os);
+    if (ctx.config.thinLTOEmitImportsFiles)
+      openFile(path + ".imports");
+  }
+}
+
 static void undefine(Symbol *s) { replaceSymbol<Undefined>(s, s->getName()); }
 
 void BitcodeCompiler::add(BitcodeFile &f) {
diff --git a/lld/COFF/LTO.h b/lld/COFF/LTO.h
index 73e855e567b09..d520911afa2ae 100644
--- a/lld/COFF/LTO.h
+++ b/lld/COFF/LTO.h
@@ -38,6 +38,8 @@ class BitcodeFile;
 class InputFile;
 class COFFLinkerContext;
 
+void thinLTOCreateEmptyIndexFiles(COFFLinkerContext &ctx);
+
 class BitcodeCompiler {
 public:
   BitcodeCompiler(COFFLinkerContext &ctx);
diff --git a/lld/test/COFF/thinlto-emit-imports.ll b/lld/test/COFF/thinlto-emit-imports.ll
index 26af017b17b2c..51936bc32935f 100644
--- a/lld/test/COFF/thinlto-emit-imports.ll
+++ b/lld/test/COFF/thinlto-emit-imports.ll
@@ -11,7 +11,8 @@
 ; Ensure lld generates imports files if requested for distributed backends.
 ; RUN: rm -f %t3.obj.imports %t3.obj.thinlto.bc
 ; RUN: lld-link -entry:main -thinlto-index-only \
-; RUN:     -thinlto-emit-imports-files %t1.obj %t2.obj %t3.obj -out:%t4.exe
+; RUN:     -thinlto-emit-imports-files %t1.obj \
+; RUN:     -start-lib %t2.obj %t3.obj -end-lib -out:%t4.exe
 
 ; The imports file for this module contains the bitcode file for
 ; Inputs/thinlto.ll
@@ -26,7 +27,8 @@
 ; RUN: cat %t3.obj.imports | count 0
 
 ; The index file should be created even for the input with an empty summary.
-; RUN: ls %t3.obj.thinlto.bc
+; RUN: llvm-dis %t3.obj.thinlto.bc -o - | FileCheck %s --check-prefix=UNUSED
+; UNUSED: ^{{[0-9]+}} = flags: 2
 
 ; Ensure lld generates error if unable to write to imports file.
 ; RUN: rm -f %t3.obj.imports



More information about the llvm-commits mailing list