Skip to content

Commit 82a3646

Browse files
authored
[LLD][COFF] Add support for ARM64EC auxiliary IAT (#108304)
In addition to the regular IAT, ARM64EC also includes an auxiliary IAT. At runtime, the regular IAT is populated with the addresses of imported functions, which may be x86_64 functions or the export thunks of ARM64EC functions. The auxiliary IAT contains versions of functions that are guaranteed to be directly callable by ARM64 code. The linker fills the auxiliary IAT with the addresses of `__impchk_` thunks. These thunks perform a call on the IAT address using `__icall_helper_arm64ec` with the target address from the IAT. If the imported function is an ARM64EC function, the OS may replace the address in the auxiliary IAT with the address of the ARM64EC version of the function (not its export thunk), avoiding the runtime call checker for better performance.
1 parent 7e9bd12 commit 82a3646

File tree

11 files changed

+135
-23
lines changed

11 files changed

+135
-23
lines changed

lld/COFF/DLL.cpp

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,30 @@ class NullChunk : public NonSectionChunk {
142142
size_t size;
143143
};
144144

145+
// A chunk for ARM64EC auxiliary IAT.
146+
class AuxImportChunk : public NonSectionChunk {
147+
public:
148+
explicit AuxImportChunk(ImportFile *file) : file(file) {
149+
setAlignment(sizeof(uint64_t));
150+
}
151+
size_t getSize() const override { return sizeof(uint64_t); }
152+
153+
void writeTo(uint8_t *buf) const override {
154+
uint64_t impchkVA = 0;
155+
if (file->impchkThunk)
156+
impchkVA = file->impchkThunk->getRVA() + file->ctx.config.imageBase;
157+
write64le(buf, impchkVA);
158+
}
159+
160+
void getBaserels(std::vector<Baserel> *res) override {
161+
if (file->impchkThunk)
162+
res->emplace_back(rva, file->ctx.config.machine);
163+
}
164+
165+
private:
166+
ImportFile *file;
167+
};
168+
145169
static std::vector<std::vector<DefinedImportData *>>
146170
binImports(COFFLinkerContext &ctx,
147171
const std::vector<DefinedImportData *> &imports) {
@@ -160,7 +184,15 @@ binImports(COFFLinkerContext &ctx,
160184
// Sort symbols by name for each group.
161185
std::vector<DefinedImportData *> &syms = kv.second;
162186
llvm::sort(syms, [](DefinedImportData *a, DefinedImportData *b) {
163-
return a->getName() < b->getName();
187+
auto getBaseName = [](DefinedImportData *sym) {
188+
StringRef name = sym->getName();
189+
name.consume_front("__imp_");
190+
// Skip aux_ part of ARM64EC function symbol name.
191+
if (sym->file->impchkThunk)
192+
name.consume_front("aux_");
193+
return name;
194+
};
195+
return getBaseName(a) < getBaseName(b);
164196
});
165197
v.push_back(std::move(syms));
166198
}
@@ -687,16 +719,24 @@ void IdataContents::create(COFFLinkerContext &ctx) {
687719
if (s->getExternalName().empty()) {
688720
lookups.push_back(make<OrdinalOnlyChunk>(ctx, ord));
689721
addresses.push_back(make<OrdinalOnlyChunk>(ctx, ord));
690-
continue;
722+
} else {
723+
auto *c = make<HintNameChunk>(s->getExternalName(), ord);
724+
lookups.push_back(make<LookupChunk>(ctx, c));
725+
addresses.push_back(make<LookupChunk>(ctx, c));
726+
hints.push_back(c);
727+
}
728+
729+
if (s->file->impECSym) {
730+
auto chunk = make<AuxImportChunk>(s->file);
731+
auxIat.push_back(chunk);
732+
s->file->impECSym->setLocation(chunk);
691733
}
692-
auto *c = make<HintNameChunk>(s->getExternalName(), ord);
693-
lookups.push_back(make<LookupChunk>(ctx, c));
694-
addresses.push_back(make<LookupChunk>(ctx, c));
695-
hints.push_back(c);
696734
}
697735
// Terminate with null values.
698736
lookups.push_back(make<NullChunk>(ctx.config.wordsize));
699737
addresses.push_back(make<NullChunk>(ctx.config.wordsize));
738+
if (ctx.config.machine == ARM64EC)
739+
auxIat.push_back(make<NullChunk>(ctx.config.wordsize));
700740

701741
for (int i = 0, e = syms.size(); i < e; ++i)
702742
syms[i]->setLocation(addresses[base + i]);

lld/COFF/DLL.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ class IdataContents {
3131
std::vector<Chunk *> addresses;
3232
std::vector<Chunk *> hints;
3333
std::vector<Chunk *> dllNames;
34+
std::vector<Chunk *> auxIat;
3435
};
3536

3637
// Windows-specific.

lld/COFF/Driver.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2447,6 +2447,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
24472447
ctx.symtab.addAbsolute("__arm64x_extra_rfe_table_size", 0);
24482448
ctx.symtab.addAbsolute("__arm64x_redirection_metadata", 0);
24492449
ctx.symtab.addAbsolute("__arm64x_redirection_metadata_count", 0);
2450+
ctx.symtab.addAbsolute("__hybrid_auxiliary_iat", 0);
24502451
ctx.symtab.addAbsolute("__hybrid_code_map", 0);
24512452
ctx.symtab.addAbsolute("__hybrid_code_map_count", 0);
24522453
ctx.symtab.addAbsolute("__x64_code_ranges_to_entry_points", 0);

lld/COFF/InputFiles.cpp

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,19 +1071,39 @@ void ImportFile::parse() {
10711071
this->hdr = hdr;
10721072
externalName = extName;
10731073

1074-
impSym = ctx.symtab.addImportData(impName, this);
1074+
bool isCode = hdr->getType() == llvm::COFF::IMPORT_CODE;
1075+
1076+
if (ctx.config.machine != ARM64EC) {
1077+
impSym = ctx.symtab.addImportData(impName, this, location);
1078+
} else {
1079+
// In addition to the regular IAT, ARM64EC also contains an auxiliary IAT,
1080+
// which holds addresses that are guaranteed to be callable directly from
1081+
// ARM64 code. Function symbol naming is swapped: __imp_ symbols refer to
1082+
// the auxiliary IAT, while __imp_aux_ symbols refer to the regular IAT. For
1083+
// data imports, the naming is reversed.
1084+
StringRef auxImpName = saver().save("__imp_aux_" + name);
1085+
if (isCode) {
1086+
impSym = ctx.symtab.addImportData(auxImpName, this, location);
1087+
impECSym = ctx.symtab.addImportData(impName, this, auxLocation);
1088+
} else {
1089+
impSym = ctx.symtab.addImportData(impName, this, location);
1090+
impECSym = ctx.symtab.addImportData(auxImpName, this, auxLocation);
1091+
}
1092+
if (!impECSym)
1093+
return;
1094+
}
10751095
// If this was a duplicate, we logged an error but may continue;
10761096
// in this case, impSym is nullptr.
10771097
if (!impSym)
10781098
return;
10791099

10801100
if (hdr->getType() == llvm::COFF::IMPORT_CONST)
1081-
static_cast<void>(ctx.symtab.addImportData(name, this));
1101+
static_cast<void>(ctx.symtab.addImportData(name, this, location));
10821102

10831103
// If type is function, we need to create a thunk which jump to an
10841104
// address pointed by the __imp_ symbol. (This allows you to call
10851105
// DLL functions just like regular non-DLL functions.)
1086-
if (hdr->getType() == llvm::COFF::IMPORT_CODE) {
1106+
if (isCode) {
10871107
if (ctx.config.machine != ARM64EC) {
10881108
thunkSym = ctx.symtab.addImportThunk(name, impSym, makeImportThunk());
10891109
} else {

lld/COFF/InputFiles.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,10 @@ class ImportFile : public InputFile {
362362
const coff_import_header *hdr;
363363
Chunk *location = nullptr;
364364

365+
// Auxiliary IAT symbol and chunk on ARM64EC.
366+
DefinedImportData *impECSym = nullptr;
367+
Chunk *auxLocation = nullptr;
368+
365369
// We want to eliminate dllimported symbols if no one actually refers to them.
366370
// These "Live" bits are used to keep track of which import library members
367371
// are actually in use.

lld/COFF/SymbolTable.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -584,7 +584,7 @@ void SymbolTable::initializeECThunks() {
584584

585585
Symbol *sym = exitThunks.lookup(file->thunkSym);
586586
if (!sym)
587-
sym = exitThunks.lookup(file->impSym);
587+
sym = exitThunks.lookup(file->impECSym);
588588
file->impchkThunk->exitThunk = dyn_cast_or_null<Defined>(sym);
589589
}
590590
}
@@ -785,11 +785,12 @@ Symbol *SymbolTable::addCommon(InputFile *f, StringRef n, uint64_t size,
785785
return s;
786786
}
787787

788-
DefinedImportData *SymbolTable::addImportData(StringRef n, ImportFile *f) {
788+
DefinedImportData *SymbolTable::addImportData(StringRef n, ImportFile *f,
789+
Chunk *&location) {
789790
auto [s, wasInserted] = insert(n, nullptr);
790791
s->isUsedInRegularObj = true;
791792
if (wasInserted || isa<Undefined>(s) || s->isLazy()) {
792-
replaceSymbol<DefinedImportData>(s, n, f);
793+
replaceSymbol<DefinedImportData>(s, n, f, location);
793794
return cast<DefinedImportData>(s);
794795
}
795796

lld/COFF/SymbolTable.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,8 @@ class SymbolTable {
103103
Symbol *addCommon(InputFile *f, StringRef n, uint64_t size,
104104
const llvm::object::coff_symbol_generic *s = nullptr,
105105
CommonChunk *c = nullptr);
106-
DefinedImportData *addImportData(StringRef n, ImportFile *f);
106+
DefinedImportData *addImportData(StringRef n, ImportFile *f,
107+
Chunk *&location);
107108
Symbol *addImportThunk(StringRef name, DefinedImportData *s,
108109
ImportThunkChunk *chunk);
109110
void addLibcall(StringRef name);

lld/COFF/Symbols.h

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -354,23 +354,23 @@ class Undefined : public Symbol {
354354
// table in an output. The former has "__imp_" prefix.
355355
class DefinedImportData : public Defined {
356356
public:
357-
DefinedImportData(StringRef n, ImportFile *f)
358-
: Defined(DefinedImportDataKind, n), file(f) {
359-
}
357+
DefinedImportData(StringRef n, ImportFile *file, Chunk *&location)
358+
: Defined(DefinedImportDataKind, n), file(file), location(location) {}
360359

361360
static bool classof(const Symbol *s) {
362361
return s->kind() == DefinedImportDataKind;
363362
}
364363

365-
uint64_t getRVA() { return file->location->getRVA(); }
366-
Chunk *getChunk() { return file->location; }
367-
void setLocation(Chunk *addressTable) { file->location = addressTable; }
364+
uint64_t getRVA() { return getChunk()->getRVA(); }
365+
Chunk *getChunk() { return location; }
366+
void setLocation(Chunk *addressTable) { location = addressTable; }
368367

369368
StringRef getDLLName() { return file->dllName; }
370369
StringRef getExternalName() { return file->externalName; }
371370
uint16_t getOrdinal() { return file->hdr->OrdinalHint; }
372371

373372
ImportFile *file;
373+
Chunk *&location;
374374

375375
// This is a pointer to the synthetic symbol associated with the load thunk
376376
// for this symbol that will be called if the DLL is delay-loaded. This is

lld/COFF/Writer.cpp

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -914,6 +914,8 @@ void Writer::addSyntheticIdata() {
914914
if (!idata.hints.empty())
915915
add(".idata$6", idata.hints);
916916
add(".idata$7", idata.dllNames);
917+
if (!idata.auxIat.empty())
918+
add(".idata$9", idata.auxIat);
917919
}
918920

919921
void Writer::appendECImportTables() {
@@ -936,6 +938,15 @@ void Writer::appendECImportTables() {
936938
rdataSec->contribSections.insert(rdataSec->contribSections.begin(),
937939
importAddresses);
938940
}
941+
942+
// The auxiliary IAT is always placed at the end of the .rdata section
943+
// and is aligned to 4KB.
944+
if (PartialSection *auxIat = findPartialSection(".idata$9", rdata)) {
945+
auxIat->chunks.front()->setAlignment(0x1000);
946+
rdataSec->chunks.insert(rdataSec->chunks.end(), auxIat->chunks.begin(),
947+
auxIat->chunks.end());
948+
rdataSec->addContributingPartialSection(auxIat);
949+
}
939950
}
940951

941952
// Locate the first Chunk and size of the import directory list and the
@@ -1095,7 +1106,8 @@ void Writer::createSections() {
10951106

10961107
// ARM64EC has specific placement and alignment requirements for the IAT.
10971108
// Delay adding its chunks until appendECImportTables.
1098-
if (isArm64EC(ctx.config.machine) && pSec->name == ".idata$5")
1109+
if (isArm64EC(ctx.config.machine) &&
1110+
(pSec->name == ".idata$5" || pSec->name == ".idata$9"))
10991111
continue;
11001112

11011113
OutputSection *sec = createSection(name, outChars);
@@ -2254,6 +2266,11 @@ void Writer::setECSymbols() {
22542266
Symbol *entryPointCountSym =
22552267
ctx.symtab.findUnderscore("__arm64x_redirection_metadata_count");
22562268
cast<DefinedAbsolute>(entryPointCountSym)->setVA(exportThunks.size());
2269+
2270+
Symbol *iatSym = ctx.symtab.findUnderscore("__hybrid_auxiliary_iat");
2271+
replaceSymbol<DefinedSynthetic>(iatSym, "__hybrid_auxiliary_iat",
2272+
idata.auxIat.empty() ? nullptr
2273+
: idata.auxIat.front());
22572274
}
22582275

22592276
// Write section contents to a mmap'ed file.

lld/test/COFF/Inputs/loadconfig-arm64ec.s

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ __chpe_metadata:
7676
.rva __os_arm64x_check_icall
7777
.rva __os_arm64x_check_icall_cfg
7878
.word 0 // __arm64x_native_entrypoint
79-
.word 0 // __hybrid_auxiliary_iat
79+
.rva __hybrid_auxiliary_iat
8080
.word __x64_code_ranges_to_entry_points_count
8181
.word __arm64x_redirection_metadata_count
8282
.rva __os_arm64x_get_x64_information

lld/test/COFF/arm64ec-import.test

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,37 @@ DISASM-NEXT: 180002000: ff 25 02 10 00 00 jmpq *0x1002(%rip)
6363

6464
RUN: llvm-readobj --hex-dump=.test out.dll | FileCheck --check-prefix=TESTSEC %s
6565
RUN: llvm-readobj --hex-dump=.test out2.dll | FileCheck --check-prefix=TESTSEC %s
66-
TESTSEC: 0x180006000 08300000 00300000 10300000 20300000
67-
TESTSEC-NEXT: 0x180006010 08100000 1c100000 00200000
66+
TESTSEC: 0x180007000 08500000 00300000 10500000 20500000
67+
TESTSEC-NEXT: 0x180007010 08300000 00500000 10300000 20300000
68+
TESTSEC-NEXT: 0x180007020 08100000 1c100000 00200000
6869

6970
RUN: llvm-readobj --headers out.dll | FileCheck -check-prefix=HEADERS %s
7071
HEADERS: LoadConfigTableRVA: 0x4010
7172
HEADERS: IATRVA: 0x3000
7273
HEADERS: IATSize: 0x1000
7374

75+
RUN: llvm-readobj --coff-load-config out.dll | FileCheck -check-prefix=LOADCONFIG %s
76+
LOADCONFIG: AuxiliaryIAT: 0x5000
77+
78+
RUN: llvm-readobj --hex-dump=.rdata out.dll | FileCheck -check-prefix=RDATA %s
79+
RDATA: 0x180005000 00000000 00000000 08100080 01000000
80+
RDATA-NEXT: 0x180005010 1c100080 01000000 00000000 00000000
81+
RDATA-NEXT: 0x180005020 30100080 01000000 00000000 00000000
82+
83+
RUN: llvm-readobj --coff-basereloc out.dll | FileCheck -check-prefix=BASERELOC %s
84+
BASERELOC: BaseReloc [
85+
BASERELOC-NOT: Address: 0x5000
86+
BASERELOC: Address: 0x5008
87+
BASERELOC-NEXT: }
88+
BASERELOC-NEXT: Entry {
89+
BASERELOC-NEXT: Type: DIR64
90+
BASERELOC-NEXT: Address: 0x5010
91+
BASERELOC-NEXT: }
92+
BASERELOC-NEXT: Entry {
93+
BASERELOC-NEXT: Type: DIR64
94+
BASERELOC-NEXT: Address: 0x5020
95+
BASERELOC-NEXT: }
96+
7497
#--- test.s
7598
.section .test, "r"
7699
.globl arm64ec_data_sym
@@ -80,6 +103,10 @@ arm64ec_data_sym:
80103
.rva __imp_data
81104
.rva __imp_func2
82105
.rva __imp_t2func
106+
.rva __imp_aux_func
107+
.rva __imp_aux_data
108+
.rva __imp_aux_func2
109+
.rva __imp_aux_t2func
83110
.rva __impchk_func
84111
.rva __impchk_func2
85112
.rva func

0 commit comments

Comments
 (0)