From 61b06773b79c59095f29804da123f4d053e58713 Mon Sep 17 00:00:00 2001 From: Jacek Caban Date: Wed, 29 Apr 2026 12:33:42 +0200 Subject: [PATCH] [LLD][COFF] Use lazy object mechanism instead of relying on the archive map for thin archives on ARM64EC (#194349) 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. This uses the same mechanism as when thin archive members are passed with -start-lib/-end-lib, where symbols are added to the symbol table without pulling in the object file unless it is referenced. 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. --- lld/COFF/Driver.cpp | 31 +++++---- lld/COFF/Driver.h | 6 +- lld/test/COFF/arm64ec-thin-lib.s | 105 +++++++++++++++++++++++++++++++ 3 files changed, 128 insertions(+), 14 deletions(-) create mode 100644 lld/test/COFF/arm64ec-thin-lib.s diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index c08ffbd8ae83..024cb2c95cd2 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -294,16 +294,24 @@ void LinkerDriver::addBuffer(std::unique_ptr mb, case file_magic::archive: { std::unique_ptr file = CHECK(Archive::create(mbref), filename + ": failed to parse archive"); - if (wholeArchive) { + + // 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::move(file)); // take ownership int memberIndex = 0; for (MemoryBufferRef m : getArchiveMembers(ctx, archive)) { if (!archive->isThin()) - addArchiveBuffer(m, "", filename, memberIndex++); + addArchiveBuffer(m, "", filename, memberIndex++, + !wholeArchive); else - addThinArchiveBuffer(m, ""); + addThinArchiveBuffer(m, "", !wholeArchive); } return; @@ -418,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(ctx, mb); @@ -429,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?"; @@ -448,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, @@ -478,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; } @@ -496,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 e0c447cfc7f8..e7a7acebc6e4 100644 --- a/lld/COFF/Driver.h +++ b/lld/COFF/Driver.h @@ -178,8 +178,10 @@ private: void addBuffer(std::unique_ptr 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 task); bool run(); diff --git a/lld/test/COFF/arm64ec-thin-lib.s b/lld/test/COFF/arm64ec-thin-lib.s new file mode 100644 index 000000000000..d41bdbf1b30e --- /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 +