Skip to content

Commit dbcded7

Browse files
committed
[LLD][COFF] Add support for including native ARM64 objects in ARM64EC images
MSVC linker accepts native ARM64 object files as input with -machine:arm64ec, similar to -machine:arm64x. Its usefulness is very limited; for example, both exports and imports are not reflected in the PE structures and can't work. However, their symbol tables are otherwise functional. Since we already have handling of multiple symbol tables implemented for ARM64X, the required changes are mostly about adjusting relevant checks to account for them on the ARM64EC target. Delay-load helper handling is a bit of a shortcut. The patch never pulls it for native object files and just ensures that the code is fine with that. In general, I think it would be nice to adjust the driver to pull it only when it's actually referenced, which would allow applying the same logic to the native symbol table on ARM64EC without worrying about pulling too much.
1 parent 064f9d0 commit dbcded7

23 files changed

+252
-141
lines changed

lld/COFF/COFFLinkerContext.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,14 @@ class COFFLinkerContext : public CommonLinkerContext {
5050
f(symtab);
5151
}
5252

53+
// Invoke the specified callback for each active symbol table,
54+
// skipping the native symbol table on pure ARM64EC targets.
55+
void forEachActiveSymtab(std::function<void(SymbolTable &symtab)> f) {
56+
if (symtab.ctx.config.machine == ARM64X)
57+
f(*hybridSymtab);
58+
f(symtab);
59+
}
60+
5361
std::vector<ObjFile *> objFileInstances;
5462
std::map<std::string, PDBInputFile *> pdbInputFileInstances;
5563
std::vector<ImportFile *> importFileInstances;

lld/COFF/Chunks.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,7 @@ void SectionChunk::getBaserels(std::vector<Baserel> *res) {
580580
// to match the value in the EC load config, which is expected to be
581581
// a relocatable pointer to the __chpe_metadata symbol.
582582
COFFLinkerContext &ctx = file->symtab.ctx;
583-
if (ctx.hybridSymtab && ctx.hybridSymtab->loadConfigSym &&
583+
if (ctx.config.machine == ARM64X && ctx.hybridSymtab->loadConfigSym &&
584584
ctx.hybridSymtab->loadConfigSym->getChunk() == this &&
585585
ctx.symtab.loadConfigSym &&
586586
ctx.hybridSymtab->loadConfigSize >=

lld/COFF/DLL.cpp

Lines changed: 43 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,8 @@ class TailMergeChunkARM64 : public NonSectionCodeChunk {
560560
memcpy(buf, tailMergeARM64, sizeof(tailMergeARM64));
561561
applyArm64Addr(buf + 44, desc->getRVA(), rva + 44, 12);
562562
applyArm64Imm(buf + 48, desc->getRVA() & 0xfff, 0);
563-
applyArm64Branch26(buf + 52, helper->getRVA() - rva - 52);
563+
if (helper)
564+
applyArm64Branch26(buf + 52, helper->getRVA() - rva - 52);
564565
}
565566

566567
Chunk *desc = nullptr;
@@ -781,6 +782,7 @@ void IdataContents::create(COFFLinkerContext &ctx) {
781782
// ordinal values to the table.
782783
size_t base = lookups.size();
783784
Chunk *lookupsTerminator = nullptr, *addressesTerminator = nullptr;
785+
uint32_t nativeOnly = 0;
784786
for (DefinedImportData *s : syms) {
785787
uint16_t ord = s->getOrdinal();
786788
HintNameChunk *hintChunk = nullptr;
@@ -806,8 +808,8 @@ void IdataContents::create(COFFLinkerContext &ctx) {
806808
// the native terminator, they will be ignored in the native view.
807809
// In the EC view, they should act as terminators, so emit ZEROFILL
808810
// relocations overriding them.
809-
if (ctx.hybridSymtab && !lookupsTerminator && s->file->isEC() &&
810-
!s->file->hybridFile) {
811+
if (ctx.config.machine == ARM64X && !lookupsTerminator &&
812+
s->file->isEC() && !s->file->hybridFile) {
811813
lookupsTerminator = lookupsChunk;
812814
addressesTerminator = addressesChunk;
813815
lookupsChunk = make<NullChunk>(ctx);
@@ -841,6 +843,7 @@ void IdataContents::create(COFFLinkerContext &ctx) {
841843
// Fill the auxiliary IAT with null chunks for native-only imports.
842844
auxIat.push_back(make<NullChunk>(ctx));
843845
auxIatCopy.push_back(make<NullChunk>(ctx));
846+
++nativeOnly;
844847
}
845848
}
846849
// Terminate with null values.
@@ -862,18 +865,15 @@ void IdataContents::create(COFFLinkerContext &ctx) {
862865
// Create the import table header.
863866
dllNames.push_back(make<StringChunk>(syms[0]->getDLLName()));
864867
auto *dir = make<ImportDirectoryChunk>(dllNames.back());
865-
dir->lookupTab = lookups[base];
866-
dir->addressTab = addresses[base];
867-
dirs.push_back(dir);
868868

869-
if (ctx.hybridSymtab) {
870-
// If native-only imports exist, they will appear as a prefix to all
871-
// imports. Emit ARM64X relocations to skip them in the EC view.
872-
uint32_t nativeOnly =
873-
llvm::find_if(syms,
874-
[](DefinedImportData *s) { return s->file->isEC(); }) -
875-
syms.begin();
876-
if (nativeOnly) {
869+
if (ctx.hybridSymtab && nativeOnly) {
870+
if (ctx.config.machine != ARM64X)
871+
// On pure ARM64EC targets, skip native-only imports in the import
872+
// directory.
873+
base += nativeOnly;
874+
else if (nativeOnly) {
875+
// If native-only imports exist, they will appear as a prefix to all
876+
// imports. Emit ARM64X relocations to skip them in the EC view.
877877
ctx.dynamicRelocs->add(
878878
IMAGE_DVRT_ARM64X_FIXUP_TYPE_DELTA, 0,
879879
Arm64XRelocVal(
@@ -886,6 +886,10 @@ void IdataContents::create(COFFLinkerContext &ctx) {
886886
nativeOnly * sizeof(uint64_t));
887887
}
888888
}
889+
890+
dir->lookupTab = lookups[base];
891+
dir->addressTab = addresses[base];
892+
dirs.push_back(dir);
889893
}
890894
// Add null terminator.
891895
dirs.push_back(make<NullChunk>(sizeof(ImportDirectoryTableEntry), 4));
@@ -922,21 +926,25 @@ void DelayLoadContents::create() {
922926

923927
size_t base = addresses.size();
924928
ctx.forEachSymtab([&](SymbolTable &symtab) {
925-
if (ctx.hybridSymtab && symtab.isEC()) {
926-
// For hybrid images, emit null-terminated native import entries
927-
// followed by null-terminated EC entries. If a view is missing imports
928-
// for a given module, only terminators are emitted. Emit ARM64X
929-
// relocations to skip native entries in the EC view.
930-
ctx.dynamicRelocs->add(
931-
IMAGE_DVRT_ARM64X_FIXUP_TYPE_DELTA, 0,
932-
Arm64XRelocVal(dir, offsetof(delay_import_directory_table_entry,
933-
DelayImportAddressTable)),
934-
(addresses.size() - base) * sizeof(uint64_t));
935-
ctx.dynamicRelocs->add(
936-
IMAGE_DVRT_ARM64X_FIXUP_TYPE_DELTA, 0,
937-
Arm64XRelocVal(dir, offsetof(delay_import_directory_table_entry,
938-
DelayImportNameTable)),
939-
(addresses.size() - base) * sizeof(uint64_t));
929+
if (symtab.isEC()) {
930+
if (ctx.config.machine == ARM64X) {
931+
// For hybrid images, emit null-terminated native import entries
932+
// followed by null-terminated EC entries. If a view is missing
933+
// imports for a given module, only terminators are emitted. Emit
934+
// ARM64X relocations to skip native entries in the EC view.
935+
ctx.dynamicRelocs->add(
936+
IMAGE_DVRT_ARM64X_FIXUP_TYPE_DELTA, 0,
937+
Arm64XRelocVal(dir, offsetof(delay_import_directory_table_entry,
938+
DelayImportAddressTable)),
939+
(addresses.size() - base) * sizeof(uint64_t));
940+
ctx.dynamicRelocs->add(
941+
IMAGE_DVRT_ARM64X_FIXUP_TYPE_DELTA, 0,
942+
Arm64XRelocVal(dir, offsetof(delay_import_directory_table_entry,
943+
DelayImportNameTable)),
944+
(addresses.size() - base) * sizeof(uint64_t));
945+
} else {
946+
base = addresses.size();
947+
}
940948
}
941949

942950
Chunk *tm = nullptr;
@@ -981,7 +989,7 @@ void DelayLoadContents::create() {
981989
chunk = make<AuxImportChunk>(s->file);
982990
auxIatCopy.push_back(chunk);
983991
s->file->auxImpCopySym->setLocation(chunk);
984-
} else if (ctx.hybridSymtab) {
992+
} else if (ctx.config.machine == ARM64X) {
985993
// Fill the auxiliary IAT with null chunks for native imports.
986994
auxIat.push_back(make<NullChunk>(ctx));
987995
auxIatCopy.push_back(make<NullChunk>(ctx));
@@ -995,6 +1003,10 @@ void DelayLoadContents::create() {
9951003
symtab.addSynthetic(tmName, tm);
9961004
}
9971005

1006+
// Skip terminators on pure ARM64EC target if there are no native imports.
1007+
if (!tm && !symtab.isEC() && ctx.config.machine != ARM64X)
1008+
return;
1009+
9981010
// Terminate with null values.
9991011
addresses.push_back(make<NullChunk>(ctx, 8));
10001012
names.push_back(make<NullChunk>(ctx, 8));
@@ -1024,7 +1036,7 @@ void DelayLoadContents::create() {
10241036
}
10251037

10261038
Chunk *DelayLoadContents::newTailMergeChunk(SymbolTable &symtab, Chunk *dir) {
1027-
auto helper = cast<Defined>(symtab.delayLoadHelper);
1039+
auto helper = cast_or_null<Defined>(symtab.delayLoadHelper);
10281040
switch (symtab.machine) {
10291041
case AMD64:
10301042
case ARM64EC:

lld/COFF/Driver.cpp

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,6 @@ static bool compatibleMachineType(COFFLinkerContext &ctx, MachineTypes mt) {
190190
case ARM64:
191191
return mt == ARM64 || mt == ARM64X;
192192
case ARM64EC:
193-
return isArm64EC(mt) || mt == AMD64;
194193
case ARM64X:
195194
return isAnyArm64(mt) || mt == AMD64;
196195
case IMAGE_FILE_MACHINE_UNKNOWN:
@@ -499,7 +498,7 @@ void LinkerDriver::parseDirectives(InputFile *file) {
499498
case OPT_entry:
500499
if (!arg->getValue()[0])
501500
Fatal(ctx) << "missing entry point symbol name";
502-
ctx.forEachSymtab([&](SymbolTable &symtab) {
501+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
503502
symtab.entry = symtab.addGCRoot(symtab.mangle(arg->getValue()), true);
504503
});
505504
break;
@@ -657,7 +656,7 @@ void LinkerDriver::setMachine(MachineTypes machine) {
657656

658657
ctx.config.machine = machine;
659658

660-
if (machine != ARM64X) {
659+
if (!isArm64EC(machine)) {
661660
ctx.symtab.machine = machine;
662661
} else {
663662
ctx.symtab.machine = ARM64EC;
@@ -979,7 +978,7 @@ void LinkerDriver::createImportLibrary(bool asLib) {
979978
};
980979

981980
getExports(ctx.symtab, exports);
982-
if (ctx.hybridSymtab)
981+
if (ctx.config.machine == ARM64X)
983982
getExports(*ctx.hybridSymtab, nativeExports);
984983

985984
std::string libName = getImportName(asLib);
@@ -1383,13 +1382,13 @@ void LinkerDriver::maybeExportMinGWSymbols(const opt::InputArgList &args) {
13831382
return;
13841383

13851384
if (ctx.symtab.hadExplicitExports ||
1386-
(ctx.hybridSymtab && ctx.hybridSymtab->hadExplicitExports))
1385+
(ctx.config.machine == ARM64X && ctx.hybridSymtab->hadExplicitExports))
13871386
return;
13881387
if (args.hasArg(OPT_exclude_all_symbols))
13891388
return;
13901389
}
13911390

1392-
ctx.forEachSymtab([&](SymbolTable &symtab) {
1391+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
13931392
AutoExporter exporter(symtab, excludedSymbols);
13941393

13951394
for (auto *arg : args.filtered(OPT_wholearchive_file))
@@ -2304,7 +2303,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
23042303
if (auto *arg = args.getLastArg(OPT_deffile)) {
23052304
// parseModuleDefs mutates Config object.
23062305
ctx.symtab.parseModuleDefs(arg->getValue());
2307-
if (ctx.hybridSymtab) {
2306+
if (ctx.config.machine == ARM64X) {
23082307
// MSVC ignores the /defArm64Native argument on non-ARM64X targets.
23092308
// It is also ignored if the /def option is not specified.
23102309
if (auto *arg = args.getLastArg(OPT_defarm64native))
@@ -2331,7 +2330,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
23312330
}
23322331

23332332
// Handle /entry and /dll
2334-
ctx.forEachSymtab([&](SymbolTable &symtab) {
2333+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
23352334
llvm::TimeTraceScope timeScope("Entry point");
23362335
if (auto *arg = args.getLastArg(OPT_entry)) {
23372336
if (!arg->getValue()[0])
@@ -2363,7 +2362,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
23632362
llvm::TimeTraceScope timeScope("Delay load");
23642363
for (auto *arg : args.filtered(OPT_delayload)) {
23652364
config->delayLoads.insert(StringRef(arg->getValue()).lower());
2366-
ctx.forEachSymtab([&](SymbolTable &symtab) {
2365+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
23672366
if (symtab.machine == I386) {
23682367
symtab.delayLoadHelper = symtab.addGCRoot("___delayLoadHelper2@8");
23692368
} else {
@@ -2533,7 +2532,9 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
25332532
u->setWeakAlias(symtab.addUndefined(to));
25342533
}
25352534
}
2535+
});
25362536

2537+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
25372538
// If any inputs are bitcode files, the LTO code generator may create
25382539
// references to library functions that are not explicit in the bitcode
25392540
// file's symbol table. If any of those library functions are defined in
@@ -2545,7 +2546,6 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
25452546
for (auto *s : lto::LTO::getRuntimeLibcallSymbols(TT))
25462547
symtab.addLibcall(s);
25472548
}
2548-
25492549
// Windows specific -- if __load_config_used can be resolved, resolve
25502550
// it.
25512551
if (symtab.findUnderscore("_load_config_used"))
@@ -2563,7 +2563,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
25632563

25642564
// Handle /includeglob
25652565
for (StringRef pat : args::getStrings(args, OPT_incl_glob))
2566-
ctx.forEachSymtab(
2566+
ctx.forEachActiveSymtab(
25672567
[&](SymbolTable &symtab) { symtab.addUndefinedGlob(pat); });
25682568

25692569
// Create wrapped symbols for -wrap option.
@@ -2680,12 +2680,12 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
26802680
// need to create a .lib file. In MinGW mode, we only do that when the
26812681
// -implib option is given explicitly, for compatibility with GNU ld.
26822682
if (config->dll || !ctx.symtab.exports.empty() ||
2683-
(ctx.hybridSymtab && !ctx.hybridSymtab->exports.empty())) {
2683+
(ctx.config.machine == ARM64X && !ctx.hybridSymtab->exports.empty())) {
26842684
llvm::TimeTraceScope timeScope("Create .lib exports");
2685-
ctx.forEachSymtab([](SymbolTable &symtab) { symtab.fixupExports(); });
2685+
ctx.forEachActiveSymtab([](SymbolTable &symtab) { symtab.fixupExports(); });
26862686
if (!config->noimplib && (!config->mingw || !config->implib.empty()))
26872687
createImportLibrary(/*asLib=*/false);
2688-
ctx.forEachSymtab(
2688+
ctx.forEachActiveSymtab(
26892689
[](SymbolTable &symtab) { symtab.assignExportOrdinals(); });
26902690
}
26912691

@@ -2751,7 +2751,8 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
27512751

27522752
if (ctx.symtab.isEC())
27532753
ctx.symtab.initializeECThunks();
2754-
ctx.forEachSymtab([](SymbolTable &symtab) { symtab.initializeLoadConfig(); });
2754+
ctx.forEachActiveSymtab(
2755+
[](SymbolTable &symtab) { symtab.initializeLoadConfig(); });
27552756

27562757
// Identify unreferenced COMDAT sections.
27572758
if (config->doGC) {

lld/COFF/InputFiles.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,8 @@ void ArchiveFile::parse() {
137137
ctx.symtab.addLazyArchive(this, sym);
138138

139139
// Read both EC and native symbols on ARM64X.
140-
if (!ctx.hybridSymtab)
141-
return;
142140
archiveSymtab = &*ctx.hybridSymtab;
143-
} else if (ctx.hybridSymtab) {
141+
} else {
144142
// If the ECSYMBOLS section is missing in the archive, the archive could
145143
// be either a native-only ARM64 or x86_64 archive. Check the machine type
146144
// of the object containing a symbol to determine which symbol table to

lld/COFF/SymbolTable.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -551,7 +551,7 @@ void SymbolTable::initializeLoadConfig() {
551551
Warn(ctx) << "EC version of '_load_config_used' is missing";
552552
return;
553553
}
554-
if (ctx.hybridSymtab) {
554+
if (ctx.config.machine == ARM64X) {
555555
Warn(ctx) << "native version of '_load_config_used' is missing for "
556556
"ARM64X target";
557557
return;

lld/COFF/Writer.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,7 +1369,7 @@ void Writer::createExportTable() {
13691369
}
13701370
}
13711371
}
1372-
ctx.forEachSymtab([&](SymbolTable &symtab) {
1372+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
13731373
if (symtab.edataStart) {
13741374
if (symtab.hadExplicitExports)
13751375
Warn(ctx) << "literal .edata sections override exports";
@@ -1759,7 +1759,8 @@ template <typename PEHeaderTy> void Writer::writeHeader() {
17591759
assert(coffHeaderOffset == buf - buffer->getBufferStart());
17601760
auto *coff = reinterpret_cast<coff_file_header *>(buf);
17611761
buf += sizeof(*coff);
1762-
SymbolTable &symtab = ctx.hybridSymtab ? *ctx.hybridSymtab : ctx.symtab;
1762+
SymbolTable &symtab =
1763+
ctx.config.machine == ARM64X ? *ctx.hybridSymtab : ctx.symtab;
17631764
coff->Machine = symtab.isEC() ? AMD64 : symtab.machine;
17641765
coff->NumberOfSections = ctx.outputSections.size();
17651766
coff->Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE;
@@ -2391,7 +2392,7 @@ void Writer::setECSymbols() {
23912392
return a.first->getRVA() < b.first->getRVA();
23922393
});
23932394

2394-
ChunkRange &chpePdata = ctx.hybridSymtab ? hybridPdata : pdata;
2395+
ChunkRange &chpePdata = ctx.config.machine == ARM64X ? hybridPdata : pdata;
23952396
Symbol *rfeTableSym = ctx.symtab.findUnderscore("__arm64x_extra_rfe_table");
23962397
replaceSymbol<DefinedSynthetic>(rfeTableSym, "__arm64x_extra_rfe_table",
23972398
chpePdata.first);
@@ -2436,7 +2437,7 @@ void Writer::setECSymbols() {
24362437
delayIdata.getAuxIatCopy().empty() ? nullptr
24372438
: delayIdata.getAuxIatCopy().front());
24382439

2439-
if (ctx.hybridSymtab) {
2440+
if (ctx.config.machine == ARM64X) {
24402441
// For the hybrid image, set the alternate entry point to the EC entry
24412442
// point. In the hybrid view, it is swapped to the native entry point
24422443
// using ARM64X relocations.
@@ -2826,7 +2827,7 @@ void Writer::fixTlsAlignment() {
28262827
}
28272828

28282829
void Writer::prepareLoadConfig() {
2829-
ctx.forEachSymtab([&](SymbolTable &symtab) {
2830+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
28302831
if (!symtab.loadConfigSym)
28312832
return;
28322833

@@ -2886,7 +2887,7 @@ void Writer::prepareLoadConfig(SymbolTable &symtab, T *loadConfig) {
28862887
IF_CONTAINS(CHPEMetadataPointer) {
28872888
// On ARM64X, only the EC version of the load config contains
28882889
// CHPEMetadataPointer. Copy its value to the native load config.
2889-
if (ctx.hybridSymtab && !symtab.isEC() &&
2890+
if (ctx.config.machine == ARM64X && !symtab.isEC() &&
28902891
ctx.symtab.loadConfigSize >=
28912892
offsetof(T, CHPEMetadataPointer) + sizeof(T::CHPEMetadataPointer)) {
28922893
OutputSection *sec =

lld/test/COFF/arm64ec-entry-mangle.test

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ RUN: not lld-link -machine:arm64ec -dll -out:test.dll demangled-func.obj loadcon
9797
RUN: "-entry:#func" 2>&1 | FileCheck -check-prefix=FUNC-NOT-FOUND %s
9898
RUN: not lld-link -machine:arm64ec -dll -out:test.dll demangled-func.obj loadconfig-arm64ec.obj \
9999
RUN: -noentry "-export:#func" 2>&1 | FileCheck -check-prefix=FUNC-NOT-FOUND %s
100-
FUNC-NOT-FOUND: undefined symbol: #func
100+
FUNC-NOT-FOUND: undefined symbol: #func (EC symbol)
101101

102102
Verify that the linker recognizes the demangled x86_64 _DllMainCRTStartup.
103103
RUN: lld-link -machine:arm64ec -dll -out:test.dll x64-dll-main.obj loadconfig-arm64ec.obj

0 commit comments

Comments
 (0)