Skip to content

Commit 5b05728

Browse files
authored
[LLD][COFF] Add support for including native ARM64 objects in ARM64EC images (#137653)
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 4ba8f4e commit 5b05728

23 files changed

+256
-140
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: 20 additions & 14 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,9 +656,13 @@ 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 {
662+
// Set up a hybrid symbol table on ARM64EC/ARM64X. This is primarily useful
663+
// on ARM64X, where both the native and EC symbol tables are meaningful.
664+
// However, since ARM64EC can include native object files, we also need to
665+
// support a hybrid symbol table there.
663666
ctx.symtab.machine = ARM64EC;
664667
ctx.hybridSymtab.emplace(ctx, ARM64);
665668
}
@@ -979,7 +982,7 @@ void LinkerDriver::createImportLibrary(bool asLib) {
979982
};
980983

981984
getExports(ctx.symtab, exports);
982-
if (ctx.hybridSymtab)
985+
if (ctx.config.machine == ARM64X)
983986
getExports(*ctx.hybridSymtab, nativeExports);
984987

985988
std::string libName = getImportName(asLib);
@@ -1383,13 +1386,13 @@ void LinkerDriver::maybeExportMinGWSymbols(const opt::InputArgList &args) {
13831386
return;
13841387

13851388
if (ctx.symtab.hadExplicitExports ||
1386-
(ctx.hybridSymtab && ctx.hybridSymtab->hadExplicitExports))
1389+
(ctx.config.machine == ARM64X && ctx.hybridSymtab->hadExplicitExports))
13871390
return;
13881391
if (args.hasArg(OPT_exclude_all_symbols))
13891392
return;
13901393
}
13911394

1392-
ctx.forEachSymtab([&](SymbolTable &symtab) {
1395+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
13931396
AutoExporter exporter(symtab, excludedSymbols);
13941397

13951398
for (auto *arg : args.filtered(OPT_wholearchive_file))
@@ -2305,7 +2308,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
23052308
if (auto *arg = args.getLastArg(OPT_deffile)) {
23062309
// parseModuleDefs mutates Config object.
23072310
ctx.symtab.parseModuleDefs(arg->getValue());
2308-
if (ctx.hybridSymtab) {
2311+
if (ctx.config.machine == ARM64X) {
23092312
// MSVC ignores the /defArm64Native argument on non-ARM64X targets.
23102313
// It is also ignored if the /def option is not specified.
23112314
if (auto *arg = args.getLastArg(OPT_defarm64native))
@@ -2332,7 +2335,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
23322335
}
23332336

23342337
// Handle /entry and /dll
2335-
ctx.forEachSymtab([&](SymbolTable &symtab) {
2338+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
23362339
llvm::TimeTraceScope timeScope("Entry point");
23372340
if (auto *arg = args.getLastArg(OPT_entry)) {
23382341
if (!arg->getValue()[0])
@@ -2364,7 +2367,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
23642367
llvm::TimeTraceScope timeScope("Delay load");
23652368
for (auto *arg : args.filtered(OPT_delayload)) {
23662369
config->delayLoads.insert(StringRef(arg->getValue()).lower());
2367-
ctx.forEachSymtab([&](SymbolTable &symtab) {
2370+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
23682371
if (symtab.machine == I386) {
23692372
symtab.delayLoadHelper = symtab.addGCRoot("___delayLoadHelper2@8");
23702373
} else {
@@ -2538,7 +2541,9 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
25382541
u->setWeakAlias(symtab.addUndefined(to));
25392542
}
25402543
}
2544+
});
25412545

2546+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
25422547
// If any inputs are bitcode files, the LTO code generator may create
25432548
// references to library functions that are not explicit in the bitcode
25442549
// file's symbol table. If any of those library functions are defined in
@@ -2568,7 +2573,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
25682573

25692574
// Handle /includeglob
25702575
for (StringRef pat : args::getStrings(args, OPT_incl_glob))
2571-
ctx.forEachSymtab(
2576+
ctx.forEachActiveSymtab(
25722577
[&](SymbolTable &symtab) { symtab.addUndefinedGlob(pat); });
25732578

25742579
// Create wrapped symbols for -wrap option.
@@ -2685,12 +2690,12 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
26852690
// need to create a .lib file. In MinGW mode, we only do that when the
26862691
// -implib option is given explicitly, for compatibility with GNU ld.
26872692
if (config->dll || !ctx.symtab.exports.empty() ||
2688-
(ctx.hybridSymtab && !ctx.hybridSymtab->exports.empty())) {
2693+
(ctx.config.machine == ARM64X && !ctx.hybridSymtab->exports.empty())) {
26892694
llvm::TimeTraceScope timeScope("Create .lib exports");
2690-
ctx.forEachSymtab([](SymbolTable &symtab) { symtab.fixupExports(); });
2695+
ctx.forEachActiveSymtab([](SymbolTable &symtab) { symtab.fixupExports(); });
26912696
if (!config->noimplib && (!config->mingw || !config->implib.empty()))
26922697
createImportLibrary(/*asLib=*/false);
2693-
ctx.forEachSymtab(
2698+
ctx.forEachActiveSymtab(
26942699
[](SymbolTable &symtab) { symtab.assignExportOrdinals(); });
26952700
}
26962701

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

27572762
if (ctx.symtab.isEC())
27582763
ctx.symtab.initializeECThunks();
2759-
ctx.forEachSymtab([](SymbolTable &symtab) { symtab.initializeLoadConfig(); });
2764+
ctx.forEachActiveSymtab(
2765+
[](SymbolTable &symtab) { symtab.initializeLoadConfig(); });
27602766

27612767
// Identify unreferenced COMDAT sections.
27622768
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
@@ -1374,7 +1374,7 @@ void Writer::createExportTable() {
13741374
}
13751375
}
13761376
}
1377-
ctx.forEachSymtab([&](SymbolTable &symtab) {
1377+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
13781378
if (symtab.edataStart) {
13791379
if (symtab.hadExplicitExports)
13801380
Warn(ctx) << "literal .edata sections override exports";
@@ -1776,7 +1776,8 @@ template <typename PEHeaderTy> void Writer::writeHeader() {
17761776
assert(coffHeaderOffset == buf - buffer->getBufferStart());
17771777
auto *coff = reinterpret_cast<coff_file_header *>(buf);
17781778
buf += sizeof(*coff);
1779-
SymbolTable &symtab = ctx.hybridSymtab ? *ctx.hybridSymtab : ctx.symtab;
1779+
SymbolTable &symtab =
1780+
ctx.config.machine == ARM64X ? *ctx.hybridSymtab : ctx.symtab;
17801781
coff->Machine = symtab.isEC() ? AMD64 : symtab.machine;
17811782
coff->NumberOfSections = ctx.outputSections.size();
17821783
coff->Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE;
@@ -2433,7 +2434,7 @@ void Writer::setECSymbols() {
24332434
return a.first->getRVA() < b.first->getRVA();
24342435
});
24352436

2436-
ChunkRange &chpePdata = ctx.hybridSymtab ? hybridPdata : pdata;
2437+
ChunkRange &chpePdata = ctx.config.machine == ARM64X ? hybridPdata : pdata;
24372438
Symbol *rfeTableSym = ctx.symtab.findUnderscore("__arm64x_extra_rfe_table");
24382439
replaceSymbol<DefinedSynthetic>(rfeTableSym, "__arm64x_extra_rfe_table",
24392440
chpePdata.first);
@@ -2478,7 +2479,7 @@ void Writer::setECSymbols() {
24782479
delayIdata.getAuxIatCopy().empty() ? nullptr
24792480
: delayIdata.getAuxIatCopy().front());
24802481

2481-
if (ctx.hybridSymtab) {
2482+
if (ctx.config.machine == ARM64X) {
24822483
// For the hybrid image, set the alternate entry point to the EC entry
24832484
// point. In the hybrid view, it is swapped to the native entry point
24842485
// using ARM64X relocations.
@@ -2868,7 +2869,7 @@ void Writer::fixTlsAlignment() {
28682869
}
28692870

28702871
void Writer::prepareLoadConfig() {
2871-
ctx.forEachSymtab([&](SymbolTable &symtab) {
2872+
ctx.forEachActiveSymtab([&](SymbolTable &symtab) {
28722873
if (!symtab.loadConfigSym)
28732874
return;
28742875

@@ -2928,7 +2929,7 @@ void Writer::prepareLoadConfig(SymbolTable &symtab, T *loadConfig) {
29282929
IF_CONTAINS(CHPEMetadataPointer) {
29292930
// On ARM64X, only the EC version of the load config contains
29302931
// CHPEMetadataPointer. Copy its value to the native load config.
2931-
if (ctx.hybridSymtab && !symtab.isEC() &&
2932+
if (ctx.config.machine == ARM64X && !symtab.isEC() &&
29322933
ctx.symtab.loadConfigSize >=
29332934
offsetof(T, CHPEMetadataPointer) + sizeof(T::CHPEMetadataPointer)) {
29342935
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)