[lld] [LLD][COFF] Use lazy object mechanism instead of relying on the archive map for thin archives on ARM64EC (PR #194349)

via llvm-commits llvm-commits at lists.llvm.org
Mon Apr 27 04:34:58 PDT 2026


llvmbot wrote:


<!--LLVM PR SUMMARY COMMENT-->

@llvm/pr-subscribers-lld

Author: Jacek Caban (cjacek)

<details>
<summary>Changes</summary>

On ARM64EC/ARM64X, an archive may contain both native and EC symbols in the symbol table, which can potentially conflict. Regular archives handle this using the extended archive format, which stores the EC symbol table in a separate section, but this is not available for thin archives.
    
Work around this limitation by lazily parsing all thin archive members instead of relying on the archive symbol table.
    
Fixing this at the archive format level would require changes to the format. Currently, the ECSYMBOLS section is supported only by the COFF archive format, while thin archives require the GNU format. We would either need to extend the COFF format to support thin archives or introduce ECSYMBOLS support in the GNU format.

---
Full diff: https://github.com/llvm/llvm-project/pull/194349.diff


5 Files Affected:

- (modified) lld/COFF/Driver.cpp (+24-16) 
- (modified) lld/COFF/Driver.h (+4-2) 
- (modified) lld/COFF/InputFiles.cpp (+5-5) 
- (modified) lld/COFF/InputFiles.h (+2-1) 
- (added) lld/test/COFF/arm64ec-thin-lib.s (+105) 


``````````diff
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 6d0973c1d0b78..024cb2c95cd20 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -291,25 +291,34 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
   case file_magic::windows_resource:
     resources.push_back(mbref);
     break;
-  case file_magic::archive:
-    if (wholeArchive) {
-      std::unique_ptr<Archive> file =
-          CHECK(Archive::create(mbref), filename + ": failed to parse archive");
+  case file_magic::archive: {
+    std::unique_ptr<Archive> file =
+        CHECK(Archive::create(mbref), filename + ": failed to parse archive");
+
+    // On ARM64EC/ARM64X, the archive may contain both, potentially conflicting,
+    // native and EC symbols in the symbol table. Regular archives handle this
+    // using the extended archive format, which stores the EC symbol table in a
+    // separate section, but it is not available for thin archives.
+    // Work around this limitation by lazily parsing all thin archive members
+    // instead of relying on the archive symbol table.
+    if (wholeArchive || (ctx.symtab.isEC() && file->isThin())) {
       Archive *archive = file.get();
       make<std::unique_ptr<Archive>>(std::move(file)); // take ownership
 
       int memberIndex = 0;
       for (MemoryBufferRef m : getArchiveMembers(ctx, archive)) {
         if (!archive->isThin())
-          addArchiveBuffer(m, "<whole-archive>", filename, memberIndex++);
+          addArchiveBuffer(m, "<whole-archive>", filename, memberIndex++,
+                           !wholeArchive);
         else
-          addThinArchiveBuffer(m, "<whole-archive>");
+          addThinArchiveBuffer(m, "<whole-archive>", !wholeArchive);
       }
 
       return;
     }
-    addFile(make<ArchiveFile>(ctx, mbref));
+    addFile(make<ArchiveFile>(ctx, mbref, file));
     break;
+  }
   case file_magic::bitcode:
     addFile(BitcodeFile::create(ctx, mbref, "", 0, lazy));
     break;
@@ -417,7 +426,7 @@ void LinkerDriver::enqueuePath(StringRef path, bool lazy, InputOpt inputOpt) {
 
 void LinkerDriver::addArchiveBuffer(MemoryBufferRef mb, StringRef symName,
                                     StringRef parentName,
-                                    uint64_t offsetInArchive) {
+                                    uint64_t offsetInArchive, bool lazy) {
   file_magic magic = identify_magic(mb.getBuffer());
   if (magic == file_magic::coff_import_library) {
     InputFile *imp = make<ImportFile>(ctx, mb);
@@ -428,11 +437,9 @@ void LinkerDriver::addArchiveBuffer(MemoryBufferRef mb, StringRef symName,
 
   InputFile *obj;
   if (magic == file_magic::coff_object) {
-    obj = tryCreateFatLTOFile(ctx, mb, parentName, offsetInArchive,
-                              /*lazy=*/false);
+    obj = tryCreateFatLTOFile(ctx, mb, parentName, offsetInArchive, lazy);
   } else if (magic == file_magic::bitcode) {
-    obj = BitcodeFile::create(ctx, mb, parentName, offsetInArchive,
-                              /*lazy=*/false);
+    obj = BitcodeFile::create(ctx, mb, parentName, offsetInArchive, lazy);
   } else if (magic == file_magic::coff_cl_gl_object) {
     Err(ctx) << mb.getBufferIdentifier()
              << ": is not a native COFF file. Recompile without /GL?";
@@ -447,12 +454,13 @@ void LinkerDriver::addArchiveBuffer(MemoryBufferRef mb, StringRef symName,
   Log(ctx) << "Loaded " << obj << " for " << symName;
 }
 
-void LinkerDriver::addThinArchiveBuffer(MemoryBufferRef mb, StringRef symName) {
+void LinkerDriver::addThinArchiveBuffer(MemoryBufferRef mb, StringRef symName,
+                                        bool lazy) {
   // Pass an empty string as the archive name and an offset of 0 so that
   // the original filename is used as the buffer identifier. This is
   // useful for DTLTO, where having the member identifier be the actual
   // path on disk enables distribution of bitcode files during ThinLTO.
-  addArchiveBuffer(mb, symName, /*parentName=*/"", /*OffsetInArchive=*/0);
+  addArchiveBuffer(mb, symName, /*parentName=*/"", /*OffsetInArchive=*/0, lazy);
 }
 
 void LinkerDriver::enqueueArchiveMember(const Archive::Child &c,
@@ -477,7 +485,7 @@ void LinkerDriver::enqueueArchiveMember(const Archive::Child &c,
     enqueueTask([=]() {
       llvm::TimeTraceScope timeScope("Archive: ", mb.getBufferIdentifier());
       ctx.driver.addArchiveBuffer(mb, toCOFFString(ctx, sym), parentName,
-                                  offsetInArchive);
+                                  offsetInArchive, false);
     });
     return;
   }
@@ -495,7 +503,7 @@ void LinkerDriver::enqueueArchiveMember(const Archive::Child &c,
     llvm::TimeTraceScope timeScope("Archive: ",
                                    mbOrErr.first->getBufferIdentifier());
     ctx.driver.addThinArchiveBuffer(takeBuffer(std::move(mbOrErr.first)),
-                                    toCOFFString(ctx, sym));
+                                    toCOFFString(ctx, sym), false);
   });
 }
 
diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h
index e0c447cfc7f80..e7a7acebc6e4c 100644
--- a/lld/COFF/Driver.h
+++ b/lld/COFF/Driver.h
@@ -178,8 +178,10 @@ class LinkerDriver {
   void addBuffer(std::unique_ptr<MemoryBuffer> mb, bool wholeArchive,
                  bool lazy);
   void addArchiveBuffer(MemoryBufferRef mbref, StringRef symName,
-                        StringRef parentName, uint64_t offsetInArchive);
-  void addThinArchiveBuffer(MemoryBufferRef mbref, StringRef symName);
+                        StringRef parentName, uint64_t offsetInArchive,
+                        bool lazy);
+  void addThinArchiveBuffer(MemoryBufferRef mbref, StringRef symName,
+                            bool lazy);
 
   void enqueueTask(std::function<void()> task);
   bool run();
diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp
index 5e66ff182cd96..0c29e7f670550 100644
--- a/lld/COFF/InputFiles.cpp
+++ b/lld/COFF/InputFiles.cpp
@@ -150,16 +150,16 @@ static bool fixupDllMain(COFFLinkerContext &ctx, llvm::object::Archive *file,
   return false;
 }
 
-ArchiveFile::ArchiveFile(COFFLinkerContext &ctx, MemoryBufferRef m)
-    : InputFile(ctx.symtab, ArchiveKind, m) {}
+ArchiveFile::ArchiveFile(COFFLinkerContext &ctx, MemoryBufferRef m,
+                         std::unique_ptr<Archive> &f)
+    : InputFile(ctx.symtab, ArchiveKind, m) {
+  file.swap(f);
+}
 
 void ArchiveFile::parse() {
   COFFLinkerContext &ctx = symtab.ctx;
   SymbolTable *archiveSymtab = &symtab;
 
-  // Parse a MemoryBufferRef as an archive file.
-  file = CHECK(Archive::create(mb), this);
-
   // Try to read symbols from ECSYMBOLS section on ARM64EC.
   if (ctx.symtab.isEC()) {
     iterator_range<Archive::symbol_iterator> symbols =
diff --git a/lld/COFF/InputFiles.h b/lld/COFF/InputFiles.h
index 21b9aeef21d4f..ce8bc6705e489 100644
--- a/lld/COFF/InputFiles.h
+++ b/lld/COFF/InputFiles.h
@@ -118,7 +118,8 @@ class InputFile {
 // .lib or .a file.
 class ArchiveFile : public InputFile {
 public:
-  explicit ArchiveFile(COFFLinkerContext &ctx, MemoryBufferRef m);
+  explicit ArchiveFile(COFFLinkerContext &ctx, MemoryBufferRef mb,
+                       std::unique_ptr<Archive> &f);
   static bool classof(const InputFile *f) { return f->kind() == ArchiveKind; }
   void parse() override;
 
diff --git a/lld/test/COFF/arm64ec-thin-lib.s b/lld/test/COFF/arm64ec-thin-lib.s
new file mode 100644
index 0000000000000..d41bdbf1b30e4
--- /dev/null
+++ b/lld/test/COFF/arm64ec-thin-lib.s
@@ -0,0 +1,105 @@
+// REQUIRES: aarch64, x86
+// RUN: split-file %s %t.dir && cd %t.dir
+
+// RUN: llvm-mc -filetype=obj -triple=arm64ec-windows symref.s -o symref-arm64ec.obj
+// RUN: llvm-mc -filetype=obj -triple=aarch64-windows symref.s -o symref-aarch64.obj
+// RUN: llvm-mc -filetype=obj -triple=arm64ec-windows sym.s -o sym-arm64ec.obj
+// RUN: llvm-mc -filetype=obj -triple=aarch64-windows sym.s -o sym-aarch64.obj
+// RUN: llvm-mc -filetype=obj -triple=arm64ec-windows undefref.s -o undefref-arm64ec.obj
+// RUN: llvm-mc -filetype=obj -triple=aarch64-windows undefref.s -o undefref-aarch64.obj
+// RUN: llvm-mc -filetype=obj -triple=aarch64-windows %S/Inputs/loadconfig-arm64.s -o loadconfig-aarch64.obj
+// RUN: llvm-mc -filetype=obj -triple=arm64ec-windows %S/Inputs/loadconfig-arm64ec.s -o loadconfig-arm64ec.obj
+
+// RUN: rm -f thin.lib
+// RUN: llvm-ar rcs --thin thin.lib sym-arm64ec.obj sym-aarch64.obj undefref-arm64ec.obj undefref-aarch64.obj loadconfig-arm64ec.obj loadconfig-aarch64.obj
+
+// Test linking an ARM64EC module against a thin library containing both EC and native symbols.
+
+// RUN: lld-link -machine:arm64ec -dll -noentry -out:test-arm64ec.dll symref-arm64ec.obj thin.lib
+// RUN: llvm-readobj --coff-exports test-arm64ec.dll | FileCheck --check-prefix=EXPORTS-ARM64EC %s
+// EXPORTS-ARM64EC:      Format: COFF-ARM64EC
+// EXPORTS-ARM64EC-NEXT: Arch: aarch64
+// EXPORTS-ARM64EC-NEXT: AddressSize: 64bit
+// EXPORTS-ARM64EC-NEXT: Export {
+// EXPORTS-ARM64EC-NEXT:   Ordinal: 1
+// EXPORTS-ARM64EC-NEXT:   Name: sym
+// EXPORTS-ARM64EC-NEXT:   RVA:
+// EXPORTS-ARM64EC-NEXT: }
+
+// Test linking an ARM64X module referencing both EC and native symbols.
+
+// RUN: lld-link -machine:arm64x -dll -noentry -out:test-arm64x.dll symref-arm64ec.obj symref-aarch64.obj thin.lib
+// RUN: llvm-readobj --coff-exports test-arm64x.dll | FileCheck --check-prefix=EXPORTS-ARM64X %s
+// EXPORTS-ARM64X:      Format: COFF-ARM64X
+// EXPORTS-ARM64X-NEXT: Arch: aarch64
+// EXPORTS-ARM64X-NEXT: AddressSize: 64bit
+// EXPORTS-ARM64X-NEXT: Export {
+// EXPORTS-ARM64X-NEXT:   Ordinal: 1
+// EXPORTS-ARM64X-NEXT:   Name: sym
+// EXPORTS-ARM64X-NEXT:   RVA:
+// EXPORTS-ARM64X-NEXT: }
+// EXPORTS-ARM64X-NEXT: HybridObject {
+// EXPORTS-ARM64X-NEXT:   Format: COFF-ARM64EC
+// EXPORTS-ARM64X-NEXT:   Arch: aarch64
+// EXPORTS-ARM64X-NEXT:   AddressSize: 64bit
+// EXPORTS-ARM64X-NEXT:   Export {
+// EXPORTS-ARM64X-NEXT:     Ordinal: 1
+// EXPORTS-ARM64X-NEXT:     Name: sym
+// EXPORTS-ARM64X-NEXT:     RVA:
+// EXPORTS-ARM64X-NEXT:   }
+// EXPORTS-ARM64X-NEXT: }
+
+// Test linking an ARM64X module referencing only EC symbol.
+
+// RUN: lld-link -machine:arm64x -dll -noentry -out:test-arm64x-ecref.dll symref-arm64ec.obj thin.lib
+// RUN: llvm-readobj --coff-exports test-arm64x-ecref.dll | FileCheck --check-prefix=EXPORTS-ARM64X2 %s
+// EXPORTS-ARM64X2:      Format: COFF-ARM64X
+// EXPORTS-ARM64X2-NEXT: Arch: aarch64
+// EXPORTS-ARM64X2-NEXT: AddressSize: 64bit
+// EXPORTS-ARM64X2-NEXT: HybridObject {
+// EXPORTS-ARM64X2-NEXT:   Format: COFF-ARM64EC
+// EXPORTS-ARM64X2-NEXT:   Arch: aarch64
+// EXPORTS-ARM64X2-NEXT:   AddressSize: 64bit
+// EXPORTS-ARM64X2-NEXT:   Export {
+// EXPORTS-ARM64X2-NEXT:     Ordinal: 1
+// EXPORTS-ARM64X2-NEXT:     Name: sym
+// EXPORTS-ARM64X2-NEXT:     RVA:
+// EXPORTS-ARM64X2-NEXT:   }
+// EXPORTS-ARM64X2-NEXT: }
+
+// Test linking an ARM64X module referencing only native symbol.
+
+// RUN: lld-link -machine:arm64x -dll -noentry -out:test-arm64x-nativeref.dll symref-aarch64.obj thin.lib
+// RUN: llvm-readobj --coff-exports test-arm64x-nativeref.dll | FileCheck --check-prefix=EXPORTS-ARM64X3 %s
+// EXPORTS-ARM64X3:      Format: COFF-ARM64X
+// EXPORTS-ARM64X3-NEXT: Arch: aarch64
+// EXPORTS-ARM64X3-NEXT: AddressSize: 64bit
+// EXPORTS-ARM64X3-NEXT: Export {
+// EXPORTS-ARM64X3-NEXT:   Ordinal: 1
+// EXPORTS-ARM64X3-NEXT:   Name: sym
+// EXPORTS-ARM64X3-NEXT:   RVA:
+// EXPORTS-ARM64X3-NEXT: }
+// EXPORTS-ARM64X3-NEXT: HybridObject {
+// EXPORTS-ARM64X3-NEXT:   Format: COFF-ARM64EC
+// EXPORTS-ARM64X3-NEXT:   Arch: aarch64
+// EXPORTS-ARM64X3-NEXT:   AddressSize: 64bit
+// EXPORTS-ARM64X3-NEXT: }
+
+#--- symref.s
+    .data
+    .rva sym
+
+#--- sym.s
+     .data
+     .globl sym
+sym:
+     .word 0
+     .section .drectve, "yn"
+     .ascii " -export:sym,DATA"
+
+#--- undefref.s
+    .data
+    .globl undefref
+undefref:
+    .rva undefsym
+

``````````

</details>


https://github.com/llvm/llvm-project/pull/194349


More information about the llvm-commits mailing list