[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.
This commit is contained in:
@@ -294,16 +294,24 @@ void LinkerDriver::addBuffer(std::unique_ptr<MemoryBuffer> mb,
|
|||||||
case file_magic::archive: {
|
case file_magic::archive: {
|
||||||
std::unique_ptr<Archive> file =
|
std::unique_ptr<Archive> file =
|
||||||
CHECK(Archive::create(mbref), filename + ": failed to parse archive");
|
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();
|
Archive *archive = file.get();
|
||||||
make<std::unique_ptr<Archive>>(std::move(file)); // take ownership
|
make<std::unique_ptr<Archive>>(std::move(file)); // take ownership
|
||||||
|
|
||||||
int memberIndex = 0;
|
int memberIndex = 0;
|
||||||
for (MemoryBufferRef m : getArchiveMembers(ctx, archive)) {
|
for (MemoryBufferRef m : getArchiveMembers(ctx, archive)) {
|
||||||
if (!archive->isThin())
|
if (!archive->isThin())
|
||||||
addArchiveBuffer(m, "<whole-archive>", filename, memberIndex++);
|
addArchiveBuffer(m, "<whole-archive>", filename, memberIndex++,
|
||||||
|
!wholeArchive);
|
||||||
else
|
else
|
||||||
addThinArchiveBuffer(m, "<whole-archive>");
|
addThinArchiveBuffer(m, "<whole-archive>", !wholeArchive);
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
@@ -418,7 +426,7 @@ void LinkerDriver::enqueuePath(StringRef path, bool lazy, InputOpt inputOpt) {
|
|||||||
|
|
||||||
void LinkerDriver::addArchiveBuffer(MemoryBufferRef mb, StringRef symName,
|
void LinkerDriver::addArchiveBuffer(MemoryBufferRef mb, StringRef symName,
|
||||||
StringRef parentName,
|
StringRef parentName,
|
||||||
uint64_t offsetInArchive) {
|
uint64_t offsetInArchive, bool lazy) {
|
||||||
file_magic magic = identify_magic(mb.getBuffer());
|
file_magic magic = identify_magic(mb.getBuffer());
|
||||||
if (magic == file_magic::coff_import_library) {
|
if (magic == file_magic::coff_import_library) {
|
||||||
InputFile *imp = make<ImportFile>(ctx, mb);
|
InputFile *imp = make<ImportFile>(ctx, mb);
|
||||||
@@ -429,11 +437,9 @@ void LinkerDriver::addArchiveBuffer(MemoryBufferRef mb, StringRef symName,
|
|||||||
|
|
||||||
InputFile *obj;
|
InputFile *obj;
|
||||||
if (magic == file_magic::coff_object) {
|
if (magic == file_magic::coff_object) {
|
||||||
obj = tryCreateFatLTOFile(ctx, mb, parentName, offsetInArchive,
|
obj = tryCreateFatLTOFile(ctx, mb, parentName, offsetInArchive, lazy);
|
||||||
/*lazy=*/false);
|
|
||||||
} else if (magic == file_magic::bitcode) {
|
} else if (magic == file_magic::bitcode) {
|
||||||
obj = BitcodeFile::create(ctx, mb, parentName, offsetInArchive,
|
obj = BitcodeFile::create(ctx, mb, parentName, offsetInArchive, lazy);
|
||||||
/*lazy=*/false);
|
|
||||||
} else if (magic == file_magic::coff_cl_gl_object) {
|
} else if (magic == file_magic::coff_cl_gl_object) {
|
||||||
Err(ctx) << mb.getBufferIdentifier()
|
Err(ctx) << mb.getBufferIdentifier()
|
||||||
<< ": is not a native COFF file. Recompile without /GL?";
|
<< ": 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;
|
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
|
// 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
|
// the original filename is used as the buffer identifier. This is
|
||||||
// useful for DTLTO, where having the member identifier be the actual
|
// useful for DTLTO, where having the member identifier be the actual
|
||||||
// path on disk enables distribution of bitcode files during ThinLTO.
|
// 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,
|
void LinkerDriver::enqueueArchiveMember(const Archive::Child &c,
|
||||||
@@ -478,7 +485,7 @@ void LinkerDriver::enqueueArchiveMember(const Archive::Child &c,
|
|||||||
enqueueTask([=]() {
|
enqueueTask([=]() {
|
||||||
llvm::TimeTraceScope timeScope("Archive: ", mb.getBufferIdentifier());
|
llvm::TimeTraceScope timeScope("Archive: ", mb.getBufferIdentifier());
|
||||||
ctx.driver.addArchiveBuffer(mb, toCOFFString(ctx, sym), parentName,
|
ctx.driver.addArchiveBuffer(mb, toCOFFString(ctx, sym), parentName,
|
||||||
offsetInArchive);
|
offsetInArchive, false);
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -496,7 +503,7 @@ void LinkerDriver::enqueueArchiveMember(const Archive::Child &c,
|
|||||||
llvm::TimeTraceScope timeScope("Archive: ",
|
llvm::TimeTraceScope timeScope("Archive: ",
|
||||||
mbOrErr.first->getBufferIdentifier());
|
mbOrErr.first->getBufferIdentifier());
|
||||||
ctx.driver.addThinArchiveBuffer(takeBuffer(std::move(mbOrErr.first)),
|
ctx.driver.addThinArchiveBuffer(takeBuffer(std::move(mbOrErr.first)),
|
||||||
toCOFFString(ctx, sym));
|
toCOFFString(ctx, sym), false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -178,8 +178,10 @@ private:
|
|||||||
void addBuffer(std::unique_ptr<MemoryBuffer> mb, bool wholeArchive,
|
void addBuffer(std::unique_ptr<MemoryBuffer> mb, bool wholeArchive,
|
||||||
bool lazy);
|
bool lazy);
|
||||||
void addArchiveBuffer(MemoryBufferRef mbref, StringRef symName,
|
void addArchiveBuffer(MemoryBufferRef mbref, StringRef symName,
|
||||||
StringRef parentName, uint64_t offsetInArchive);
|
StringRef parentName, uint64_t offsetInArchive,
|
||||||
void addThinArchiveBuffer(MemoryBufferRef mbref, StringRef symName);
|
bool lazy);
|
||||||
|
void addThinArchiveBuffer(MemoryBufferRef mbref, StringRef symName,
|
||||||
|
bool lazy);
|
||||||
|
|
||||||
void enqueueTask(std::function<void()> task);
|
void enqueueTask(std::function<void()> task);
|
||||||
bool run();
|
bool run();
|
||||||
|
|||||||
105
lld/test/COFF/arm64ec-thin-lib.s
Normal file
105
lld/test/COFF/arm64ec-thin-lib.s
Normal file
@@ -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
|
||||||
|
|
||||||
Reference in New Issue
Block a user