[llvm] [llvm-ar] Introduce -wholearchive linker hint. (PR #195834)

Jacek Caban via llvm-commits llvm-commits at lists.llvm.org
Tue May 5 09:20:20 PDT 2026


https://github.com/cjacek updated https://github.com/llvm/llvm-project/pull/195834

>From f7aeb52a011b88988f6891b59ef2cf8126926a19 Mon Sep 17 00:00:00 2001
From: Jacek Caban <jacek at codeweavers.com>
Date: Tue, 31 Mar 2026 20:49:56 +0200
Subject: [PATCH 1/2] [llvm-ar] Introduce -wholearchive linker hint.

This introduces a -wholearchive marker, which is a hint to the linker that
the archive is intended to be linked with whole-archive semantics. The
intention is to make it possible to create an archive that, when passed to
the linker, behaves like an object file rather than an archive. Similarly,
when adding such an archive to another archive, its children are added
instead of the archive itself.

The marker is placed at the beginning of the string table, which should
make it transparent for tools that are not aware of this extension.

This is meant to allow creating ARM64X object files. Such object files
need to contain both native ARM64 and ARM64EC object files. An archive is
a convenient format to achieve this because it makes them useful out of
the box, but to match object file semantics, we need a way to inform the
linker that it should pull those files as if both were passed as input
rather than using the regular lazy archive semantics.
---
 llvm/include/llvm/Object/Archive.h          |  5 +++
 llvm/include/llvm/Object/ArchiveWriter.h    | 23 +++++-----
 llvm/lib/Object/ArchiveWriter.cpp           | 15 ++++---
 llvm/lib/Object/COFFImportFile.cpp          |  2 +-
 llvm/lib/ToolDrivers/llvm-lib/LibDriver.cpp |  9 ++--
 llvm/test/tools/llvm-ar/wholearchive.yaml   | 47 +++++++++++++++++++++
 llvm/tools/llvm-ar/llvm-ar.cpp              | 19 ++++++---
 llvm/tools/llvm-nm/llvm-nm.cpp              |  3 ++
 8 files changed, 96 insertions(+), 27 deletions(-)
 create mode 100644 llvm/test/tools/llvm-ar/wholearchive.yaml

diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index c97018d3231d5..fac2738c3861e 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -382,6 +382,11 @@ class LLVM_ABI Archive : public Binary {
     return std::move(ThinBuffers);
   }
 
+  bool isWholeArchive() const {
+    return StringTable.starts_with(
+        StringRef("-wholearchive", strlen("-wholearchive") + 1));
+  }
+
   std::unique_ptr<AbstractArchiveMemberHeader>
   createArchiveMemberHeader(const char *RawHeaderPtr, uint64_t Size,
                             Error *Err) const;
diff --git a/llvm/include/llvm/Object/ArchiveWriter.h b/llvm/include/llvm/Object/ArchiveWriter.h
index dad150ce1d847..ebc785b9b065a 100644
--- a/llvm/include/llvm/Object/ArchiveWriter.h
+++ b/llvm/include/llvm/Object/ArchiveWriter.h
@@ -53,20 +53,21 @@ enum class SymtabWritingMode {
 LLVM_ABI void warnToStderr(Error Err);
 
 // Write an archive directly to an output stream.
-LLVM_ABI Error writeArchiveToStream(
-    raw_ostream &Out, ArrayRef<NewArchiveMember> NewMembers,
+LLVM_ABI Error
+writeArchiveToStream(raw_ostream &Out, ArrayRef<NewArchiveMember> NewMembers,
+                     SymtabWritingMode WriteSymtab, object::Archive::Kind Kind,
+                     bool Deterministic, bool Thin, bool IsWholeArchive = false,
+                     std::optional<bool> IsEC = std::nullopt,
+                     function_ref<void(Error)> Warn = warnToStderr);
+
+LLVM_ABI Error writeArchive(
+    StringRef ArcName, ArrayRef<NewArchiveMember> NewMembers,
     SymtabWritingMode WriteSymtab, object::Archive::Kind Kind,
-    bool Deterministic, bool Thin, std::optional<bool> IsEC = std::nullopt,
+    bool Deterministic, bool Thin,
+    std::unique_ptr<MemoryBuffer> OldArchiveBuf = nullptr,
+    bool IsWholeArchive = false, std::optional<bool> IsEC = std::nullopt,
     function_ref<void(Error)> Warn = warnToStderr);
 
-LLVM_ABI Error
-writeArchive(StringRef ArcName, ArrayRef<NewArchiveMember> NewMembers,
-             SymtabWritingMode WriteSymtab, object::Archive::Kind Kind,
-             bool Deterministic, bool Thin,
-             std::unique_ptr<MemoryBuffer> OldArchiveBuf = nullptr,
-             std::optional<bool> IsEC = std::nullopt,
-             function_ref<void(Error)> Warn = warnToStderr);
-
 // writeArchiveToBuffer is similar to writeArchive but returns the Archive in a
 // buffer instead of writing it out to a file.
 LLVM_ABI Expected<std::unique_ptr<MemoryBuffer>>
diff --git a/llvm/lib/Object/ArchiveWriter.cpp b/llvm/lib/Object/ArchiveWriter.cpp
index 6d2bbca179836..51e1eb2f72d9f 100644
--- a/llvm/lib/Object/ArchiveWriter.cpp
+++ b/llvm/lib/Object/ArchiveWriter.cpp
@@ -1029,7 +1029,8 @@ Error writeArchiveToStream(raw_ostream &Out,
                            ArrayRef<NewArchiveMember> NewMembers,
                            SymtabWritingMode WriteSymtab,
                            object::Archive::Kind Kind, bool Deterministic,
-                           bool Thin, std::optional<bool> IsEC,
+                           bool Thin, bool IsWholeArchive,
+                           std::optional<bool> IsEC,
                            function_ref<void(Error)> Warn) {
   assert((!Thin || !isBSDLike(Kind)) && "Only the gnu format has a thin mode");
 
@@ -1050,6 +1051,8 @@ Error writeArchiveToStream(raw_ostream &Out,
   // reference to it, thus SymbolicFile should be destroyed first.
   LLVMContext Context;
 
+  if (IsWholeArchive)
+    StringTable << "-wholearchive" << '\0';
   Expected<std::vector<MemberData>> DataOrErr = computeMemberData(
       StringTable, SymNames, Kind, Thin, Deterministic, WriteSymtab,
       isCOFFArchive(Kind) ? &SymMap : nullptr, Context, NewMembers, IsEC, Warn);
@@ -1129,7 +1132,7 @@ Error writeArchiveToStream(raw_ostream &Out,
         // Since this changes the headers, we need to recalculate everything.
         return writeArchiveToStream(Out, NewMembers, WriteSymtab,
                                     object::Archive::K_GNU64, Deterministic,
-                                    Thin, IsEC, Warn);
+                                    Thin, false, IsEC, Warn);
       case object::Archive::K_DARWIN:
         Kind = object::Archive::K_DARWIN64;
         break;
@@ -1319,7 +1322,8 @@ Error writeArchive(StringRef ArcName, ArrayRef<NewArchiveMember> NewMembers,
                    SymtabWritingMode WriteSymtab, object::Archive::Kind Kind,
                    bool Deterministic, bool Thin,
                    std::unique_ptr<MemoryBuffer> OldArchiveBuf,
-                   std::optional<bool> IsEC, function_ref<void(Error)> Warn) {
+                   bool IsWholeArchive, std::optional<bool> IsEC,
+                   function_ref<void(Error)> Warn) {
   Expected<sys::fs::TempFile> Temp =
       sys::fs::TempFile::create(ArcName + ".temp-archive-%%%%%%%.a");
   if (!Temp)
@@ -1327,7 +1331,8 @@ Error writeArchive(StringRef ArcName, ArrayRef<NewArchiveMember> NewMembers,
   raw_fd_ostream Out(Temp->FD, false);
 
   if (Error E = writeArchiveToStream(Out, NewMembers, WriteSymtab, Kind,
-                                     Deterministic, Thin, IsEC, Warn)) {
+                                     Deterministic, Thin, IsWholeArchive, IsEC,
+                                     Warn)) {
     if (Error DiscardError = Temp->discard())
       return joinErrors(std::move(E), std::move(DiscardError));
     return E;
@@ -1358,7 +1363,7 @@ writeArchiveToBuffer(ArrayRef<NewArchiveMember> NewMembers,
 
   if (Error E =
           writeArchiveToStream(ArchiveStream, NewMembers, WriteSymtab, Kind,
-                               Deterministic, Thin, std::nullopt, Warn))
+                               Deterministic, Thin, false, std::nullopt, Warn))
     return std::move(E);
 
   return std::make_unique<SmallVectorMemoryBuffer>(
diff --git a/llvm/lib/Object/COFFImportFile.cpp b/llvm/lib/Object/COFFImportFile.cpp
index 5ef5ea310766f..a8fbcb669f0d0 100644
--- a/llvm/lib/Object/COFFImportFile.cpp
+++ b/llvm/lib/Object/COFFImportFile.cpp
@@ -800,7 +800,7 @@ Error writeImportLibrary(StringRef ImportName, StringRef Path,
   return writeArchive(Path, Members, SymtabWritingMode::NormalSymtab,
                       object::Archive::K_COFF,
                       /*Deterministic*/ true, /*Thin*/ false,
-                      /*OldArchiveBuf*/ nullptr, isArm64EC(Machine));
+                      /*OldArchiveBuf*/ nullptr, false, isArm64EC(Machine));
 }
 
 } // namespace object
diff --git a/llvm/lib/ToolDrivers/llvm-lib/LibDriver.cpp b/llvm/lib/ToolDrivers/llvm-lib/LibDriver.cpp
index 15d959d7712dd..8758c7b15ddaf 100644
--- a/llvm/lib/ToolDrivers/llvm-lib/LibDriver.cpp
+++ b/llvm/lib/ToolDrivers/llvm-lib/LibDriver.cpp
@@ -524,10 +524,11 @@ int llvm::libDriverMain(ArrayRef<const char *> ArgsArr) {
                     ? SymtabWritingMode::NormalSymtab
                     : SymtabWritingMode::NoSymtab;
 
-  if (Error E = writeArchive(
-          OutputPath, Members, Symtab,
-          Thin ? object::Archive::K_GNU : object::Archive::K_COFF,
-          /*Deterministic=*/true, Thin, nullptr, COFF::isArm64EC(LibMachine))) {
+  if (Error E =
+          writeArchive(OutputPath, Members, Symtab,
+                       Thin ? object::Archive::K_GNU : object::Archive::K_COFF,
+                       /*Deterministic=*/true, Thin, nullptr, false,
+                       COFF::isArm64EC(LibMachine))) {
     handleAllErrors(std::move(E), [&](const ErrorInfoBase &EI) {
       llvm::errs() << OutputPath << ": " << EI.message() << "\n";
     });
diff --git a/llvm/test/tools/llvm-ar/wholearchive.yaml b/llvm/test/tools/llvm-ar/wholearchive.yaml
new file mode 100644
index 0000000000000..0abe59acc8769
--- /dev/null
+++ b/llvm/test/tools/llvm-ar/wholearchive.yaml
@@ -0,0 +1,47 @@
+## Test -wholearchive marker
+
+# RUN: yaml2obj %s -o %t.obj
+# RUN: yaml2obj %s -o %t2.obj
+
+## Create -wholearchive archive.
+# RUN: rm -f %t.a
+# RUN: llvm-ar cr --whole-archive %t.a %t.obj
+# RUN: llvm-nm --print-armap %t.a | FileCheck --check-prefixes=MAP,WHOLEARCH %s
+
+# MAP:       Archive map
+# MAP-NEXT:  test in wholearchive.yaml.tmp.obj
+# MAP-EMPTY:
+# WHOLEARCH:  Archive is marked with -wholearchive
+# WHOLEARCH-EMPTY:
+
+## Create an archive containing -wholearchive archive.
+# RUN: rm -f %t-merged.a
+# RUN: llvm-ar cr %t-merged.a %t.a
+# RUN: llvm-nm --print-armap %t-merged.a | FileCheck --check-prefixes=MAP,NOWHOLEARCH %s
+# NOWHOLEARCH-NOT:  Archive is marked with -wholearchive
+
+## Modify an existing archive to add a -wholearchive marker.
+# RUN: llvm-ar r --whole-archive %t-merged.a
+# RUN: llvm-nm --print-armap %t-merged.a | FileCheck --check-prefix=WHOLEARCH %s
+
+## Remove the -wholearchive marker from an existing archive.
+# RUN: llvm-ar r --whole-archive %t-merged.a
+# RUN: llvm-nm --print-armap %t-merged.a | FileCheck --check-prefix=WHOLEARCH %s
+
+--- !COFF
+header:
+  Machine:         IMAGE_FILE_MACHINE_ARM64
+  Characteristics: [  ]
+sections:
+  - Name:            .text
+    Characteristics: [ IMAGE_SCN_CNT_CODE, IMAGE_SCN_MEM_EXECUTE, IMAGE_SCN_MEM_READ ]
+    Alignment:       4
+    SectionData:     ''
+symbols:
+  - Name:            test
+    Value:           0
+    SectionNumber:   1
+    SimpleType:      IMAGE_SYM_TYPE_NULL
+    ComplexType:     IMAGE_SYM_DTYPE_FUNCTION
+    StorageClass:    IMAGE_SYM_CLASS_EXTERNAL
+...
diff --git a/llvm/tools/llvm-ar/llvm-ar.cpp b/llvm/tools/llvm-ar/llvm-ar.cpp
index 320a903b59e87..1d9fcbbc42322 100644
--- a/llvm/tools/llvm-ar/llvm-ar.cpp
+++ b/llvm/tools/llvm-ar/llvm-ar.cpp
@@ -92,6 +92,7 @@ static void printArHelp(StringRef ToolName) {
     =windows            -   windows
   --thin                - create a thin archive
   --version             - print the version and exit
+  --whole-archive       - add a hint indicating that the archive is intended to be linked with whole-archive semantics
   -X{32|64|32_64|any}   - object mode (only for AIX OS)
   @<file>               - read options from <file>
 
@@ -252,6 +253,8 @@ static std::string ArchiveName;
 // Output directory specified by --output.
 static std::string OutputDir;
 
+static bool IsWholeArchive;
+
 static std::vector<std::unique_ptr<MemoryBuffer>> ArchiveBuffers;
 static std::vector<std::unique_ptr<object::Archive>> Archives;
 
@@ -819,11 +822,10 @@ static void addMember(std::vector<NewArchiveMember> &Members,
     return;
   }
 
-  if (FlattenArchive &&
-      identify_magic(NM.Buf->getBuffer()) == file_magic::archive) {
+  if (identify_magic(NM.Buf->getBuffer()) == file_magic::archive) {
     object::Archive &Lib = readLibrary(FileName);
     // When creating thin archives, only flatten if the member is also thin.
-    if (!Thin || Lib.isThin()) {
+    if ((FlattenArchive || Lib.isWholeArchive()) && (!Thin || Lib.isThin())) {
       Error Err = Error::success();
       // Only Thin archives are recursively flattened.
       for (auto &Child : Lib.children(Err))
@@ -1075,9 +1077,9 @@ static void performWriteOperation(ArchiveOperation Operation,
     llvm_unreachable("");
   }
 
-  Error E =
-      writeArchive(ArchiveName, NewMembersP ? *NewMembersP : NewMembers, Symtab,
-                   Kind, Deterministic, Thin, std::move(OldArchiveBuf));
+  Error E = writeArchive(ArchiveName, NewMembersP ? *NewMembersP : NewMembers,
+                         Symtab, Kind, Deterministic, Thin,
+                         std::move(OldArchiveBuf), IsWholeArchive);
   failIfError(std::move(E), ArchiveName);
 }
 
@@ -1380,6 +1382,11 @@ static int ar_main(int argc, char **argv) {
       continue;
     }
 
+    if (strcmp(*ArgIt, "--whole-archive") == 0) {
+      IsWholeArchive = true;
+      continue;
+    }
+
     Match = matchFlagWithArg("format", ArgIt, Argv);
     if (Match) {
       FormatType = StringSwitch<Format>(Match)
diff --git a/llvm/tools/llvm-nm/llvm-nm.cpp b/llvm/tools/llvm-nm/llvm-nm.cpp
index a33c54914ee9d..3be602d26df94 100644
--- a/llvm/tools/llvm-nm/llvm-nm.cpp
+++ b/llvm/tools/llvm-nm/llvm-nm.cpp
@@ -2085,6 +2085,9 @@ static void dumpArchive(Archive *A, std::vector<NMSymbol> &SymbolList,
   if (ArchiveMap)
     dumpArchiveMap(A, Filename);
 
+  if (A->isWholeArchive())
+    outs() << "Archive is marked with -wholearchive\n\n";
+
   Error Err = Error::success();
   for (auto &C : A->children(Err)) {
     Expected<std::unique_ptr<Binary>> ChildOrErr = C.getAsBinary(ContextPtr);

>From e3cd1c051a7c68ebd7a5e45f7aaba22c9db82982 Mon Sep 17 00:00:00 2001
From: Jacek Caban <jacek at codeweavers.com>
Date: Tue, 5 May 2026 17:22:58 +0200
Subject: [PATCH 2/2] Use StringLiteral

---
 llvm/include/llvm/Object/Archive.h | 6 ++++--
 llvm/lib/Object/ArchiveWriter.cpp  | 2 +-
 2 files changed, 5 insertions(+), 3 deletions(-)

diff --git a/llvm/include/llvm/Object/Archive.h b/llvm/include/llvm/Object/Archive.h
index fac2738c3861e..493292a23da8e 100644
--- a/llvm/include/llvm/Object/Archive.h
+++ b/llvm/include/llvm/Object/Archive.h
@@ -382,9 +382,11 @@ class LLVM_ABI Archive : public Binary {
     return std::move(ThinBuffers);
   }
 
+  static constexpr auto kWholeArchiveString =
+      StringLiteral::withInnerNUL("-wholearchive\0");
+
   bool isWholeArchive() const {
-    return StringTable.starts_with(
-        StringRef("-wholearchive", strlen("-wholearchive") + 1));
+    return StringTable.starts_with(kWholeArchiveString);
   }
 
   std::unique_ptr<AbstractArchiveMemberHeader>
diff --git a/llvm/lib/Object/ArchiveWriter.cpp b/llvm/lib/Object/ArchiveWriter.cpp
index 51e1eb2f72d9f..b81d45082c5d2 100644
--- a/llvm/lib/Object/ArchiveWriter.cpp
+++ b/llvm/lib/Object/ArchiveWriter.cpp
@@ -1052,7 +1052,7 @@ Error writeArchiveToStream(raw_ostream &Out,
   LLVMContext Context;
 
   if (IsWholeArchive)
-    StringTable << "-wholearchive" << '\0';
+    StringTable << Archive::kWholeArchiveString;
   Expected<std::vector<MemberData>> DataOrErr = computeMemberData(
       StringTable, SymNames, Kind, Thin, Deterministic, WriteSymtab,
       isCOFFArchive(Kind) ? &SymMap : nullptr, Context, NewMembers, IsEC, Warn);



More information about the llvm-commits mailing list