[lld] [LLD][COFF] Handle --start-lib/--end-lib group in the same way as other archives (PR #136496)

Alexandre Ganea via llvm-commits llvm-commits at lists.llvm.org
Fri May 2 14:46:36 PDT 2025


https://github.com/aganea updated https://github.com/llvm/llvm-project/pull/136496

>From c541eee802d859bc884ae25c6e717bc6835849c3 Mon Sep 17 00:00:00 2001
From: Alexandre Ganea <alex_toresh at yahoo.fr>
Date: Sun, 20 Apr 2025 12:10:36 -0400
Subject: [PATCH 1/2] [LLD][COFF] Handle --start-lib/--end-lib group in the
 same way as other archives

---
 lld/COFF/Driver.cpp      | 100 +++++++++++++++++++++++----------------
 lld/COFF/Driver.h        |  18 ++++---
 lld/COFF/InputFiles.cpp  |  44 +++++++++++------
 lld/COFF/InputFiles.h    |  58 ++++++++++++++++-------
 lld/COFF/SymbolTable.cpp |  12 ++---
 5 files changed, 144 insertions(+), 88 deletions(-)

diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index e3ff647209e72..9487868f109fa 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -200,28 +200,13 @@ 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))
-      f->parseLazy();
-    else
-      cast<ObjFile>(file)->parseLazy();
-  } else {
-    file->parse();
-    if (auto *f = dyn_cast<ObjFile>(file)) {
-      ctx.objFileInstances.push_back(f);
-    } else if (auto *f = dyn_cast<BitcodeFile>(file)) {
-      if (ltoCompilationDone) {
-        Err(ctx) << "LTO object file " << toString(file)
-                 << " linked in after "
-                    "doing LTO compilation.";
-      }
-      f->symtab.bitcodeFileInstances.push_back(f);
-    } else if (auto *f = dyn_cast<ImportFile>(file)) {
-      ctx.importFileInstances.push_back(f);
-    }
+void LinkerDriver::addFile(InputFile *file, CmdLineArchive *inCmdLineArchive) {
+  if (inCmdLineArchive) {
+    inCmdLineArchive->addInputFile(file); // schedule for lazy parsing
+    return;
   }
+  Log(ctx) << "Reading " << toString(file);
+  file->maybeParse();
 
   MachineTypes mt = file->getMachineType();
   // The ARM64EC target must be explicitly specified and cannot be inferred.
@@ -259,7 +244,8 @@ MemoryBufferRef LinkerDriver::takeBuffer(std::unique_ptr<MemoryBuffer> mb) {
 }
 
 void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
-                             bool wholeArchive, bool lazy) {
+                             bool wholeArchive,
+                             CmdLineArchive *inCmdLineArchive) {
   StringRef filename = mb->getBufferIdentifier();
 
   MemoryBufferRef mbref = takeBuffer(std::move(mb));
@@ -267,9 +253,15 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
   // File type is detected by contents, not by file extension.
   switch (identify_magic(mbref.getBuffer())) {
   case file_magic::windows_resource:
+    assert(!inCmdLineArchive &&
+           "Cannot specify a RES file inside a --start-lib/--end-lib group.");
     resources.push_back(mbref);
     break;
   case file_magic::archive:
+    // FIXME: We could later support --start-lib/--end-lib groups, to allow for
+    // "extending" an existing archive/LIB.
+    assert(!inCmdLineArchive &&
+           "Cannot specify a LIB file inside a --start-lib/--end-lib group.");
     if (wholeArchive) {
       std::unique_ptr<Archive> file =
           CHECK(Archive::create(mbref), filename + ": failed to parse archive");
@@ -284,13 +276,15 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
     addFile(make<ArchiveFile>(ctx, mbref));
     break;
   case file_magic::bitcode:
-    addFile(BitcodeFile::create(ctx, mbref, "", 0, lazy));
+    addFile(BitcodeFile::create(ctx, mbref, "", 0), inCmdLineArchive);
     break;
   case file_magic::coff_object:
   case file_magic::coff_import_library:
-    addFile(ObjFile::create(ctx, mbref, lazy));
+    addFile(ObjFile::create(ctx, mbref), inCmdLineArchive);
     break;
   case file_magic::pdb:
+    assert(!inCmdLineArchive &&
+           "Cannot specify a PDB file inside a --start-lib/--end-lib group.");
     addFile(make<PDBInputFile>(ctx, mbref));
     break;
   case file_magic::coff_cl_gl_object:
@@ -299,6 +293,9 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
     break;
   case file_magic::pecoff_executable:
     if (ctx.config.mingw) {
+      assert(
+          !inCmdLineArchive &&
+          "Cannot specify a PE/EXE file inside a --start-lib/--end-lib group.");
       addFile(make<DLLFile>(ctx.symtab, mbref));
       break;
     }
@@ -315,7 +312,9 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
   }
 }
 
-void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) {
+void LinkerDriver::enqueuePath(
+    StringRef path, bool wholeArchive,
+    std::optional<std::shared_future<CmdLineArchive *>> inCmdLineArchive) {
   auto future = std::make_shared<std::future<MBErrPair>>(
       createFutureForFile(std::string(path)));
   std::string pathStr = std::string(path);
@@ -354,7 +353,9 @@ void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) {
       else
         Err(ctx) << msg << "; did you mean '" << nearest << "'";
     } else
-      ctx.driver.addBuffer(std::move(mb), wholeArchive, lazy);
+      ctx.driver.addBuffer(std::move(mb), wholeArchive,
+                           inCmdLineArchive ? inCmdLineArchive->get()
+                                            : nullptr);
   });
 }
 
@@ -373,8 +374,7 @@ void LinkerDriver::addArchiveBuffer(MemoryBufferRef mb, StringRef symName,
   if (magic == file_magic::coff_object) {
     obj = ObjFile::create(ctx, mb);
   } else if (magic == file_magic::bitcode) {
-    obj = BitcodeFile::create(ctx, mb, parentName, offsetInArchive,
-                              /*lazy=*/false);
+    obj = BitcodeFile::create(ctx, mb, parentName, offsetInArchive);
   } else if (magic == file_magic::coff_cl_gl_object) {
     Err(ctx) << mb.getBufferIdentifier()
              << ": is not a native COFF file. Recompile without /GL?";
@@ -494,7 +494,7 @@ void LinkerDriver::parseDirectives(InputFile *file) {
       break;
     case OPT_defaultlib:
       if (std::optional<StringRef> path = findLibIfNew(arg->getValue()))
-        enqueuePath(*path, false, false);
+        enqueuePath(*path);
       break;
     case OPT_entry:
       if (!arg->getValue()[0])
@@ -2177,32 +2177,50 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   // and OPT_end_lib.
   {
     llvm::TimeTraceScope timeScope2("Parse & queue inputs");
-    bool inLib = false;
+    std::optional<std::shared_future<CmdLineArchive *>> inCmdLineArchive;
     for (auto *arg : args) {
       switch (arg->getOption().getID()) {
       case OPT_end_lib:
-        if (!inLib)
+        if (!inCmdLineArchive) {
           Err(ctx) << "stray " << arg->getSpelling();
-        inLib = false;
+        } else {
+          enqueueTask([=]() { inCmdLineArchive->get()->maybeParse(); });
+          inCmdLineArchive = std::nullopt;
+        }
         break;
       case OPT_start_lib:
-        if (inLib)
+        if (inCmdLineArchive) {
           Err(ctx) << "nested " << arg->getSpelling();
-        inLib = true;
+        } else {
+          auto a = std::make_shared<std::promise<CmdLineArchive *>>();
+          inCmdLineArchive = a->get_future().share();
+          enqueueTask([&, a]() {
+            // In is important to create a fake archive here so that we
+            // remember its placement on the command-line. This will be
+            // later needed to resolve symbols in the archive order required
+            // by the MSVC specification.
+            a->set_value(make<CmdLineArchive>(
+                ctx.symtab, MemoryBufferRef({}, "<cmdline-lib>")));
+          });
+        }
         break;
       case OPT_wholearchive_file:
         if (std::optional<StringRef> path = findFileIfNew(arg->getValue()))
-          enqueuePath(*path, true, inLib);
+          enqueuePath(*path, true, inCmdLineArchive);
         break;
       case OPT_INPUT:
         if (std::optional<StringRef> path = findFileIfNew(arg->getValue()))
-          enqueuePath(*path, isWholeArchive(*path), inLib);
+          enqueuePath(*path, isWholeArchive(*path), inCmdLineArchive);
         break;
       default:
         // Ignore other options.
         break;
       }
     }
+    if (inCmdLineArchive) {
+      Warn(ctx) << "--start-lib with no --end-lib";
+      enqueueTask([=]() { inCmdLineArchive->get()->maybeParse(); });
+    }
   }
 
   // Read all input files given via the command line.
@@ -2236,7 +2254,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   // addWinSysRootLibSearchPaths(), which is why they are in a separate loop.
   for (auto *arg : args.filtered(OPT_defaultlib))
     if (std::optional<StringRef> path = findLibIfNew(arg->getValue()))
-      enqueuePath(*path, false, false);
+      enqueuePath(*path);
   run();
   if (errorCount())
     return;
@@ -2553,9 +2571,11 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
 
         if (args.hasArg(OPT_include_optional)) {
           // Handle /includeoptional
-          for (auto *arg : args.filtered(OPT_include_optional))
-            if (isa_and_nonnull<LazyArchive>(symtab.find(arg->getValue())))
+          for (auto *arg : args.filtered(OPT_include_optional)) {
+            Symbol *sym = ctx.symtab.find(arg->getValue());
+            if (sym && (isa<LazyArchive>(sym) || isa<LazyObject>(sym)))
               symtab.addGCRoot(arg->getValue());
+          }
         }
       });
     } while (run());
@@ -2720,7 +2740,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   // /manifestdependency: enables /manifest unless an explicit /manifest:no is
   // also passed.
   if (config->manifest == Configuration::Embed)
-    addBuffer(createManifestRes(), false, false);
+    addBuffer(createManifestRes(), false);
   else if (config->manifest == Configuration::SideBySide ||
            (config->manifest == Configuration::Default &&
             !config->manifestDependencies.empty()))
diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h
index 14c97a98875bf..59fbc97e1910e 100644
--- a/lld/COFF/Driver.h
+++ b/lld/COFF/Driver.h
@@ -22,6 +22,7 @@
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/TarWriter.h"
 #include "llvm/WindowsDriver/MSVCPaths.h"
+#include <future>
 #include <memory>
 #include <optional>
 #include <set>
@@ -80,7 +81,7 @@ class LinkerDriver {
 
   void linkerMain(llvm::ArrayRef<const char *> args);
 
-  void addFile(InputFile *file);
+  void addFile(InputFile *file, CmdLineArchive *inCmdLineArchive = nullptr);
 
   void addClangLibSearchPaths(const std::string &argv0);
 
@@ -88,18 +89,23 @@ class LinkerDriver {
   void enqueueArchiveMember(const Archive::Child &c, const Archive::Symbol &sym,
                             StringRef parentName);
 
-  void enqueuePDB(StringRef Path) { enqueuePath(Path, false, false); }
+  void enqueuePDB(StringRef Path) { enqueuePath(Path); }
 
   MemoryBufferRef takeBuffer(std::unique_ptr<MemoryBuffer> mb);
 
-  void enqueuePath(StringRef path, bool wholeArchive, bool lazy);
+  // Schedule a input file for reading.
+  void enqueuePath(StringRef path, bool wholeArchive = false,
+                   std::optional<std::shared_future<CmdLineArchive *>>
+                       inCmdLineArchive = std::nullopt);
+
+  void pullArm64ECIcallHelper();
 
   // Returns a list of chunks of selected symbols.
   std::vector<Chunk *> getChunks() const;
 
   std::unique_ptr<llvm::TarWriter> tar; // for /linkrepro
 
-  void pullArm64ECIcallHelper();
+  bool ltoCompilationDone = false;
 
 private:
   // Searches a file from search paths.
@@ -170,7 +176,7 @@ class LinkerDriver {
   std::set<std::string> visitedLibs;
 
   void addBuffer(std::unique_ptr<MemoryBuffer> mb, bool wholeArchive,
-                 bool lazy);
+                 CmdLineArchive *inCmdLineArchive = nullptr);
   void addArchiveBuffer(MemoryBufferRef mbref, StringRef symName,
                         StringRef parentName, uint64_t offsetInArchive);
 
@@ -258,8 +264,6 @@ class LinkerDriver {
   // Create export thunks for exported and patchable Arm64EC function symbols.
   void createECExportThunks();
   void maybeCreateECExportThunk(StringRef name, Symbol *&sym);
-
-  bool ltoCompilationDone = false;
 };
 
 // Create enum with OPT_xxx values for each option in Options.td
diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp
index 7fb42bb681939..20c4613aa422e 100644
--- a/lld/COFF/InputFiles.cpp
+++ b/lld/COFF/InputFiles.cpp
@@ -226,8 +226,19 @@ lld::coff::getArchiveMembers(COFFLinkerContext &ctx, Archive *file) {
   return v;
 }
 
-ObjFile::ObjFile(SymbolTable &symtab, COFFObjectFile *coffObj, bool lazy)
-    : InputFile(symtab, ObjectKind, coffObj->getMemoryBufferRef(), lazy),
+void CmdLineArchive::parse() {
+  for (InputFile *f : files) {
+    if (auto *o = dyn_cast<ObjFile>(f))
+      o->parseLazy();
+    else if (auto *b = dyn_cast<BitcodeFile>(f))
+      b->parseLazy();
+  }
+}
+
+void CmdLineArchive::addInputFile(InputFile *f) { files.push_back(f); }
+
+ObjFile::ObjFile(SymbolTable &symtab, COFFObjectFile *coffObj)
+    : InputFile(symtab, ObjectKind, coffObj->getMemoryBufferRef()),
       coffObj(coffObj) {}
 
 ObjFile *ObjFile::create(COFFLinkerContext &ctx, MemoryBufferRef m, bool lazy) {
@@ -241,8 +252,7 @@ ObjFile *ObjFile::create(COFFLinkerContext &ctx, MemoryBufferRef m, bool lazy) {
     Fatal(ctx) << m.getBufferIdentifier() << " is not a COFF file";
 
   bin->release();
-  return make<ObjFile>(ctx.getSymtab(MachineTypes(obj->getMachine())), obj,
-                       lazy);
+  return make<ObjFile>(ctx.getSymtab(MachineTypes(obj->getMachine())), obj);
 }
 
 void ObjFile::parseLazy() {
@@ -257,8 +267,6 @@ void ObjFile::parseLazy() {
     if (coffSym.isAbsolute() && ignoredSymbolName(name))
       continue;
     symtab.addLazyObject(this, name);
-    if (!lazy)
-      return;
     i += coffSym.getNumberOfAuxSymbols();
   }
 }
@@ -299,6 +307,8 @@ void ObjFile::initializeECThunks() {
 }
 
 void ObjFile::parse() {
+  symtab.ctx.objFileInstances.push_back(this);
+
   // Read section and symbol tables.
   initializeChunks();
   initializeSymbols();
@@ -1201,6 +1211,8 @@ ImportThunkChunk *ImportFile::makeImportThunk() {
 }
 
 void ImportFile::parse() {
+  symtab.ctx.importFileInstances.push_back(this);
+
   const auto *hdr =
       reinterpret_cast<const coff_import_header *>(mb.getBufferStart());
 
@@ -1307,14 +1319,14 @@ void ImportFile::parse() {
 }
 
 BitcodeFile::BitcodeFile(SymbolTable &symtab, MemoryBufferRef mb,
-                         std::unique_ptr<lto::InputFile> &o, bool lazy)
-    : InputFile(symtab, BitcodeKind, mb, lazy) {
+                         std::unique_ptr<lto::InputFile> &o)
+    : InputFile(symtab, BitcodeKind, mb) {
   obj.swap(o);
 }
 
 BitcodeFile *BitcodeFile::create(COFFLinkerContext &ctx, MemoryBufferRef mb,
                                  StringRef archiveName,
-                                 uint64_t offsetInArchive, bool lazy) {
+                                 uint64_t offsetInArchive) {
   std::string path = mb.getBufferIdentifier().str();
   if (ctx.config.thinLTOIndexOnly)
     path = replaceThinLTOSuffix(mb.getBufferIdentifier(),
@@ -1335,13 +1347,18 @@ BitcodeFile *BitcodeFile::create(COFFLinkerContext &ctx, MemoryBufferRef mb,
                                                utostr(offsetInArchive)));
 
   std::unique_ptr<lto::InputFile> obj = check(lto::InputFile::create(mbref));
-  return make<BitcodeFile>(ctx.getSymtab(getMachineType(obj.get())), mb, obj,
-                           lazy);
+  return make<BitcodeFile>(ctx.getSymtab(getMachineType(obj.get())), mb, obj);
 }
 
 BitcodeFile::~BitcodeFile() = default;
 
 void BitcodeFile::parse() {
+  if (symtab.ctx.driver.ltoCompilationDone) {
+    Err(symtab.ctx) << "LTO object file " << toString(this)
+                    << " linked in after doing LTO compilation.";
+  }
+  symtab.bitcodeFileInstances.push_back(this);
+
   llvm::StringSaver &saver = lld::saver();
 
   std::vector<std::pair<Symbol *, bool>> comdat(obj->getComdatTable().size());
@@ -1406,11 +1423,8 @@ void BitcodeFile::parse() {
 
 void BitcodeFile::parseLazy() {
   for (const lto::InputFile::Symbol &sym : obj->symbols())
-    if (!sym.isUndefined()) {
+    if (!sym.isUndefined())
       symtab.addLazyObject(this, sym.getName());
-      if (!lazy)
-        return;
-    }
 }
 
 MachineTypes BitcodeFile::getMachineType(const llvm::lto::InputFile *obj) {
diff --git a/lld/COFF/InputFiles.h b/lld/COFF/InputFiles.h
index 21b9aeef21d4f..09553bb9e56d5 100644
--- a/lld/COFF/InputFiles.h
+++ b/lld/COFF/InputFiles.h
@@ -71,6 +71,7 @@ class InputFile {
 public:
   enum Kind {
     ArchiveKind,
+    CmdLineArchiveKind,
     ObjectKind,
     PDBKind,
     ImportKind,
@@ -83,14 +84,19 @@ class InputFile {
   // Returns the filename.
   StringRef getName() const { return mb.getBufferIdentifier(); }
 
-  // Reads a file (the constructor doesn't do that).
-  virtual void parse() = 0;
-
   // Returns the CPU type this file was compiled to.
   virtual MachineTypes getMachineType() const {
     return IMAGE_FILE_MACHINE_UNKNOWN;
   }
 
+  // Parse the file if it wasn't already parsed.
+  void maybeParse() {
+    if (parsed)
+      return;
+    parsed = true;
+    parse();
+  }
+
   MemoryBufferRef mb;
 
   // An archive file name if this file is created from an archive.
@@ -102,17 +108,18 @@ class InputFile {
   SymbolTable &symtab;
 
 protected:
-  InputFile(SymbolTable &s, Kind k, MemoryBufferRef m, bool lazy = false)
-      : mb(m), symtab(s), fileKind(k), lazy(lazy) {}
+  InputFile(SymbolTable &s, Kind k, MemoryBufferRef m)
+      : mb(m), symtab(s), fileKind(k) {}
+
+  // Reads a file (the constructor doesn't do that).
+  virtual void parse() = 0;
 
   StringRef directives;
 
+  bool parsed = false;
+
 private:
   const Kind fileKind;
-
-public:
-  // True if this is a lazy ObjFile or BitcodeFile.
-  bool lazy = false;
 };
 
 // .lib or .a file.
@@ -132,16 +139,30 @@ class ArchiveFile : public InputFile {
   llvm::DenseSet<uint64_t> seen;
 };
 
+// A synthetic --start-lib/--end-lib archive.
+class CmdLineArchive : public InputFile {
+public:
+  explicit CmdLineArchive(SymbolTable &s, MemoryBufferRef m)
+      : InputFile(s, CmdLineArchiveKind, m) {}
+  static bool classof(const InputFile *f) {
+    return f->kind() == CmdLineArchiveKind;
+  }
+  void parse() override;
+  void addInputFile(InputFile *f);
+
+private:
+  std::vector<InputFile *> files;
+};
+
 // .obj or .o file. This may be a member of an archive file.
 class ObjFile : public InputFile {
 public:
   static ObjFile *create(COFFLinkerContext &ctx, MemoryBufferRef mb,
                          bool lazy = false);
-  explicit ObjFile(SymbolTable &symtab, COFFObjectFile *coffObj, bool lazy);
+  explicit ObjFile(SymbolTable &symtab, COFFObjectFile *coffObj);
 
   static bool classof(const InputFile *f) { return f->kind() == ObjectKind; }
   void parse() override;
-  void parseLazy();
   MachineTypes getMachineType() const override;
   ArrayRef<Chunk *> getChunks() { return chunks; }
   ArrayRef<SectionChunk *> getDebugChunks() { return debugChunks; }
@@ -191,6 +212,9 @@ class ObjFile : public InputFile {
   // True if this file was compiled with /guard:ehcont.
   bool hasGuardEHCont() { return feat00Flags & 0x4000; }
 
+  // Add lazy references to the symbols in this file.
+  void parseLazy();
+
   // Pointer to the PDB module descriptor builder. Various debug info records
   // will reference object files by "module index", which is here. Things like
   // source files and section contributions are also recorded here. Will be null
@@ -391,24 +415,24 @@ class ImportFile : public InputFile {
 class BitcodeFile : public InputFile {
 public:
   explicit BitcodeFile(SymbolTable &symtab, MemoryBufferRef mb,
-                       std::unique_ptr<llvm::lto::InputFile> &obj, bool lazy);
+                       std::unique_ptr<llvm::lto::InputFile> &obj);
   ~BitcodeFile();
 
   static BitcodeFile *create(COFFLinkerContext &ctx, MemoryBufferRef mb,
-                             StringRef archiveName, uint64_t offsetInArchive,
-                             bool lazy);
+                             StringRef archiveName, uint64_t offsetInArchive);
   static bool classof(const InputFile *f) { return f->kind() == BitcodeKind; }
   ArrayRef<Symbol *> getSymbols() { return symbols; }
   MachineTypes getMachineType() const override {
     return getMachineType(obj.get());
   }
   static MachineTypes getMachineType(const llvm::lto::InputFile *obj);
-  void parseLazy();
+  void parse() override;
   std::unique_ptr<llvm::lto::InputFile> obj;
 
-private:
-  void parse() override;
+  // Add lazy references to the symbols in this file.
+  void parseLazy();
 
+private:
   std::vector<Symbol *> symbols;
 };
 
diff --git a/lld/COFF/SymbolTable.cpp b/lld/COFF/SymbolTable.cpp
index 8fb0ee4e890d6..020b1b37cdb3c 100644
--- a/lld/COFF/SymbolTable.cpp
+++ b/lld/COFF/SymbolTable.cpp
@@ -47,6 +47,8 @@ static COFFSyncStream errorOrWarn(COFFLinkerContext &ctx) {
 
 // Causes the file associated with a lazy symbol to be linked in.
 static void forceLazy(Symbol *s) {
+  if (s->pendingArchiveLoad)
+    return;
   s->pendingArchiveLoad = true;
   switch (s->kind()) {
   case Symbol::Kind::LazyArchiveKind: {
@@ -56,11 +58,6 @@ static void forceLazy(Symbol *s) {
   }
   case Symbol::Kind::LazyObjectKind: {
     InputFile *file = cast<LazyObject>(s)->file;
-    // FIXME: Remove this once we resolve all defineds before all undefineds in
-    //        ObjFile::initializeSymbols().
-    if (!file->lazy)
-      return;
-    file->lazy = false;
     file->symtab.ctx.driver.addFile(file);
     break;
   }
@@ -747,7 +744,6 @@ void SymbolTable::addLazyArchive(ArchiveFile *f, const Archive::Symbol &sym) {
 }
 
 void SymbolTable::addLazyObject(InputFile *f, StringRef n) {
-  assert(f->lazy);
   if (isEC() && !checkLazyECPair<LazyObject>(this, n, f))
     return;
   auto [s, wasInserted] = insert(n, f);
@@ -759,7 +755,6 @@ void SymbolTable::addLazyObject(InputFile *f, StringRef n) {
   if (!u || (u->weakAlias && !u->isECAlias(machine)) || s->pendingArchiveLoad)
     return;
   s->pendingArchiveLoad = true;
-  f->lazy = false;
   ctx.driver.addFile(f);
 }
 
@@ -1372,8 +1367,7 @@ void SymbolTable::compileBitcodeFiles() {
     lto->add(*f);
   for (InputFile *newObj : lto->compile()) {
     ObjFile *obj = cast<ObjFile>(newObj);
-    obj->parse();
-    ctx.objFileInstances.push_back(obj);
+    obj->maybeParse();
   }
 }
 

>From a782451d48fb695c6964c08aaeaf9fff6ca22e51 Mon Sep 17 00:00:00 2001
From: Alexandre Ganea <alex_toresh at yahoo.fr>
Date: Fri, 2 May 2025 17:45:23 -0400
Subject: [PATCH 2/2] Change the asserts into warning and add more test
 coverage

---
 lld/COFF/Driver.cpp             | 68 ++++++++++++++++++++++-----------
 lld/COFF/Driver.h               |  4 +-
 lld/COFF/InputFiles.h           | 11 ++++--
 lld/Common/Args.cpp             | 10 +++++
 lld/include/lld/Common/Args.h   | 11 +++++-
 lld/test/COFF/start-lib-warn.ll | 55 ++++++++++++++++++++++++++
 6 files changed, 131 insertions(+), 28 deletions(-)
 create mode 100644 lld/test/COFF/start-lib-warn.ll

diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 9487868f109fa..1855969c31acd 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -250,18 +250,23 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
 
   MemoryBufferRef mbref = takeBuffer(std::move(mb));
 
+  auto maybePrintWarning = [&](StringRef type, StringRef message) {
+    if (inCmdLineArchive)
+      Warn(ctx) << type << " file provided between "
+                << inCmdLineArchive->startLibArg << "/"
+                << inCmdLineArchive->endLibArg << " " << message;
+  };
+
   // File type is detected by contents, not by file extension.
   switch (identify_magic(mbref.getBuffer())) {
   case file_magic::windows_resource:
-    assert(!inCmdLineArchive &&
-           "Cannot specify a RES file inside a --start-lib/--end-lib group.");
+    maybePrintWarning(".res", "will not be lazy");
     resources.push_back(mbref);
     break;
   case file_magic::archive:
     // FIXME: We could later support --start-lib/--end-lib groups, to allow for
     // "extending" an existing archive/LIB.
-    assert(!inCmdLineArchive &&
-           "Cannot specify a LIB file inside a --start-lib/--end-lib group.");
+    maybePrintWarning(".lib/.a", "has no effect");
     if (wholeArchive) {
       std::unique_ptr<Archive> file =
           CHECK(Archive::create(mbref), filename + ": failed to parse archive");
@@ -283,8 +288,7 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
     addFile(ObjFile::create(ctx, mbref), inCmdLineArchive);
     break;
   case file_magic::pdb:
-    assert(!inCmdLineArchive &&
-           "Cannot specify a PDB file inside a --start-lib/--end-lib group.");
+    maybePrintWarning(".pdb", "will not be lazy");
     addFile(make<PDBInputFile>(ctx, mbref));
     break;
   case file_magic::coff_cl_gl_object:
@@ -293,9 +297,7 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
     break;
   case file_magic::pecoff_executable:
     if (ctx.config.mingw) {
-      assert(
-          !inCmdLineArchive &&
-          "Cannot specify a PE/EXE file inside a --start-lib/--end-lib group.");
+      maybePrintWarning(".dll", "will not be lazy");
       addFile(make<DLLFile>(ctx.symtab, mbref));
       break;
     }
@@ -314,7 +316,7 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
 
 void LinkerDriver::enqueuePath(
     StringRef path, bool wholeArchive,
-    std::optional<std::shared_future<CmdLineArchive *>> inCmdLineArchive) {
+    std::optional<std::shared_ptr<CmdLineArchive *>> inCmdLineArchive) {
   auto future = std::make_shared<std::future<MBErrPair>>(
       createFutureForFile(std::string(path)));
   std::string pathStr = std::string(path);
@@ -354,8 +356,7 @@ void LinkerDriver::enqueuePath(
         Err(ctx) << msg << "; did you mean '" << nearest << "'";
     } else
       ctx.driver.addBuffer(std::move(mb), wholeArchive,
-                           inCmdLineArchive ? inCmdLineArchive->get()
-                                            : nullptr);
+                           inCmdLineArchive ? **inCmdLineArchive : nullptr);
   });
 }
 
@@ -2172,19 +2173,38 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
     return false;
   };
 
+  // Store start_lib/end_lib arguments in order to render dignostics in the
+  // same way the flags are written on the command line.
+  llvm::opt::Arg *startLibArg = nullptr;
+  llvm::opt::Arg *endLibArg = nullptr;
+  auto endLibSpelling = [&]() {
+    return endLibArg ? endLibArg->getSpelling()
+                     : lld::args::getOptionSpellingLikeArg(
+                           ctx.optTable, OPT_end_lib, startLibArg, ctx.saver);
+  };
+
   // Create a list of input files. These can be given as OPT_INPUT options
   // and OPT_wholearchive_file options, and we also need to track OPT_start_lib
   // and OPT_end_lib.
   {
     llvm::TimeTraceScope timeScope2("Parse & queue inputs");
-    std::optional<std::shared_future<CmdLineArchive *>> inCmdLineArchive;
+    std::optional<std::shared_ptr<CmdLineArchive *>> inCmdLineArchive;
+    auto close = [&]() {
+      enqueueTask([=]() {
+        assert(inCmdLineArchive);
+        if (CmdLineArchive *a = **inCmdLineArchive)
+          a->maybeParse();
+      });
+    };
+
     for (auto *arg : args) {
       switch (arg->getOption().getID()) {
       case OPT_end_lib:
         if (!inCmdLineArchive) {
           Err(ctx) << "stray " << arg->getSpelling();
         } else {
-          enqueueTask([=]() { inCmdLineArchive->get()->maybeParse(); });
+          endLibArg = arg;
+          close();
           inCmdLineArchive = std::nullopt;
         }
         break;
@@ -2192,15 +2212,16 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
         if (inCmdLineArchive) {
           Err(ctx) << "nested " << arg->getSpelling();
         } else {
-          auto a = std::make_shared<std::promise<CmdLineArchive *>>();
-          inCmdLineArchive = a->get_future().share();
-          enqueueTask([&, a]() {
+          startLibArg = arg;
+          inCmdLineArchive = std::make_shared<CmdLineArchive *>();
+          enqueueTask([&, inCmdLineArchive, startLibArg, endLibArg]() {
             // In is important to create a fake archive here so that we
             // remember its placement on the command-line. This will be
             // later needed to resolve symbols in the archive order required
             // by the MSVC specification.
-            a->set_value(make<CmdLineArchive>(
-                ctx.symtab, MemoryBufferRef({}, "<cmdline-lib>")));
+            **inCmdLineArchive = make<CmdLineArchive>(
+                ctx.symtab, MemoryBufferRef({}, "<cmdline-lib>"),
+                startLibArg->getSpelling(), endLibSpelling());
           });
         }
         break;
@@ -2218,8 +2239,11 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
       }
     }
     if (inCmdLineArchive) {
-      Warn(ctx) << "--start-lib with no --end-lib";
-      enqueueTask([=]() { inCmdLineArchive->get()->maybeParse(); });
+      StringRef startLib = startLibArg->getSpelling();
+      Warn(ctx) << startLib << " without " << endLibSpelling()
+                << "\nNOTE: all files provided after " << startLib
+                << " were lazy.";
+      close();
     }
   }
 
@@ -2740,7 +2764,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
   // /manifestdependency: enables /manifest unless an explicit /manifest:no is
   // also passed.
   if (config->manifest == Configuration::Embed)
-    addBuffer(createManifestRes(), false);
+    addBuffer(createManifestRes());
   else if (config->manifest == Configuration::SideBySide ||
            (config->manifest == Configuration::Default &&
             !config->manifestDependencies.empty()))
diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h
index 59fbc97e1910e..203cab930536e 100644
--- a/lld/COFF/Driver.h
+++ b/lld/COFF/Driver.h
@@ -95,7 +95,7 @@ class LinkerDriver {
 
   // Schedule a input file for reading.
   void enqueuePath(StringRef path, bool wholeArchive = false,
-                   std::optional<std::shared_future<CmdLineArchive *>>
+                   std::optional<std::shared_ptr<CmdLineArchive *>>
                        inCmdLineArchive = std::nullopt);
 
   void pullArm64ECIcallHelper();
@@ -175,7 +175,7 @@ class LinkerDriver {
 
   std::set<std::string> visitedLibs;
 
-  void addBuffer(std::unique_ptr<MemoryBuffer> mb, bool wholeArchive,
+  void addBuffer(std::unique_ptr<MemoryBuffer> mb, bool wholeArchive = false,
                  CmdLineArchive *inCmdLineArchive = nullptr);
   void addArchiveBuffer(MemoryBufferRef mbref, StringRef symName,
                         StringRef parentName, uint64_t offsetInArchive);
diff --git a/lld/COFF/InputFiles.h b/lld/COFF/InputFiles.h
index 09553bb9e56d5..6688a8344eb1a 100644
--- a/lld/COFF/InputFiles.h
+++ b/lld/COFF/InputFiles.h
@@ -139,17 +139,22 @@ class ArchiveFile : public InputFile {
   llvm::DenseSet<uint64_t> seen;
 };
 
-// A synthetic --start-lib/--end-lib archive.
+// A synthetic -start-lib/-end-lib archive.
 class CmdLineArchive : public InputFile {
 public:
-  explicit CmdLineArchive(SymbolTable &s, MemoryBufferRef m)
-      : InputFile(s, CmdLineArchiveKind, m) {}
+  explicit CmdLineArchive(SymbolTable &s, MemoryBufferRef m, StringRef startLib,
+                          StringRef endLib)
+      : InputFile(s, CmdLineArchiveKind, m), startLibArg(startLib),
+        endLibArg(endLib) {}
   static bool classof(const InputFile *f) {
     return f->kind() == CmdLineArchiveKind;
   }
   void parse() override;
   void addInputFile(InputFile *f);
 
+  StringRef startLibArg;
+  StringRef endLibArg;
+
 private:
   std::vector<InputFile *> files;
 };
diff --git a/lld/Common/Args.cpp b/lld/Common/Args.cpp
index 5546b2aece641..e64769e09d13f 100644
--- a/lld/Common/Args.cpp
+++ b/lld/Common/Args.cpp
@@ -90,3 +90,13 @@ StringRef lld::args::getFilenameWithoutExe(StringRef path) {
     return sys::path::stem(path);
   return sys::path::filename(path);
 }
+
+StringRef lld::args::getOptionSpellingLikeArg(llvm::opt::OptTable &optTable,
+                                              llvm::opt::OptSpecifier opt,
+                                              llvm::opt::Arg *arg,
+                                              llvm::StringSaver &saver) {
+  StringRef prefix = arg->getSpelling();
+  prefix.consume_back(arg->getOption().getName());
+  auto option = optTable.getOption(opt);
+  return saver.save((prefix + option.getName()).str());
+}
diff --git a/lld/include/lld/Common/Args.h b/lld/include/lld/Common/Args.h
index 60f83fbbbf1a3..6e962807a15da 100644
--- a/lld/include/lld/Common/Args.h
+++ b/lld/include/lld/Common/Args.h
@@ -15,9 +15,13 @@
 #include <vector>
 
 namespace llvm {
+class StringSaver;
 namespace opt {
+class Arg;
 class InputArgList;
-}
+class OptSpecifier;
+class OptTable;
+} // namespace opt
 } // namespace llvm
 
 namespace lld {
@@ -40,6 +44,11 @@ std::vector<StringRef> getLines(MemoryBufferRef mb);
 
 StringRef getFilenameWithoutExe(StringRef path);
 
+StringRef getOptionSpellingLikeArg(llvm::opt::OptTable &optTable,
+                                   llvm::opt::OptSpecifier opt,
+                                   llvm::opt::Arg *arg,
+                                   llvm::StringSaver &saver);
+
 } // namespace args
 } // namespace lld
 
diff --git a/lld/test/COFF/start-lib-warn.ll b/lld/test/COFF/start-lib-warn.ll
new file mode 100644
index 0000000000000..05d40e4a1a049
--- /dev/null
+++ b/lld/test/COFF/start-lib-warn.ll
@@ -0,0 +1,55 @@
+; REQUIRES: x86
+;
+; RUN: split-file %s %t.dir
+;
+; We need an input file to lld, so create one.
+; RUN: llc -filetype=obj %t.dir/main.ll -o %t.obj
+
+; RUN: lld-link -start-lib %t.obj /subsystem:console /force 2>&1 \
+; RUN:     | FileCheck --check-prefix=MISSING_END %s
+; MISSING_END: -start-lib without -end-lib
+
+; RUN: not lld-link %t.obj -end-lib /subsystem:console /force 2>&1 \
+; RUN:     | FileCheck --check-prefix=STRAY_END %s
+; STRAY_END: stray -end-lib
+
+; RUN: not lld-link -start-lib -start-lib %t.obj /subsystem:console /force 2>&1 \
+; RUN:     | FileCheck --check-prefix=NESTED_START %s
+; NESTED_START: nested -start-lib
+
+; RUN: lld-link -start-lib %t.obj %S/Inputs/resource.res -end-lib /subsystem:console /force 2>&1 \
+; RUN:     | FileCheck --check-prefix=WARN_RES %s
+; WARN_RES: .res file provided between -start-lib/-end-lib will not be lazy
+
+; RUN: lld-link -start-lib %t.obj %S/Inputs/ret42.lib -end-lib /subsystem:console /force 2>&1 \
+; RUN:     | FileCheck --check-prefix=WARN_LIB %s
+; WARN_LIB: .lib/.a file provided between -start-lib/-end-lib has no effect
+
+; RUN: lld-link -start-lib %t.obj %S/Inputs/pdb-diff-cl.pdb -end-lib /subsystem:console /force 2>&1 \
+; RUN:     | FileCheck --check-prefix=WARN_PDB %s
+; WARN_PDB: .pdb file provided between -start-lib/-end-lib will not be lazy
+
+; RUN: llvm-mc -filetype=obj -triple=x86_64-windows-gnu %t.dir/lib.s -o %t.lib.o
+; RUN: lld-link -noentry -dll -def:%t.dir/lib.def %t.lib.o -out:%t.lib.dll -implib:%t.implib.lib
+; RUN: lld-link -lldmingw -start-lib %t.lib.dll -end-lib /force 2>&1 \
+; RUN:     | FileCheck --check-prefix=WARN_EXE %s
+; WARN_EXE: .dll file provided between -start-lib/-end-lib will not be lazy
+
+#--- main.ll
+target datalayout = "e-m:w-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-pc-windows-msvc"
+
+define void @main() {
+  ret void
+}
+
+#--- lib.s
+.text
+.global func1
+func1:
+  ret
+
+#--- lib.def
+NAME lib.dll
+EXPORTS
+    func1
\ No newline at end of file



More information about the llvm-commits mailing list