Skip to content

Commit 09d5f9b

Browse files
committed
[LLD][COFF] Add support for ARM64EC import call thunks.
These thunks can be accessed using __impchk_* symbols, though they are typically not called directly. Instead, they are used to populate the auxiliary IAT. When the imported function is x86_64 (or an ARM64EC function with a patched export thunk), the thunk is used to call it. Otherwise, the OS may replace the thunk at runtime with a direct pointer to the ARM64EC function to avoid the overhead.
1 parent 6cc442f commit 09d5f9b

File tree

13 files changed

+176
-19
lines changed

13 files changed

+176
-19
lines changed

lld/COFF/Chunks.cpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1093,4 +1093,26 @@ void CHPERedirectionChunk::writeTo(uint8_t *buf) const {
10931093
}
10941094
}
10951095

1096+
ImportThunkChunkARM64EC::ImportThunkChunkARM64EC(ImportFile *file)
1097+
: ImportThunkChunk(file->ctx, file->impSym), file(file) {}
1098+
1099+
void ImportThunkChunkARM64EC::writeTo(uint8_t *buf) const {
1100+
memcpy(buf, importThunkARM64EC, sizeof(importThunkARM64EC));
1101+
applyArm64Addr(buf, file->impSym->getRVA(), rva, 12);
1102+
applyArm64Ldr(buf + 4, file->impSym->getRVA() & 0xfff);
1103+
1104+
// The exit thunk may be missing. This can happen if the application only
1105+
// references a function by its address (in which case the thunk is never
1106+
// actually used, but is still required to fill the auxiliary IAT), or in
1107+
// cases of hand-written assembly calling an imported ARM64EC function (where
1108+
// the exit thunk is ignored by __icall_helper_arm64ec). In such cases, MSVC
1109+
// link.exe uses 0 as the RVA.
1110+
uint32_t exitThunkRVA = exitThunk ? exitThunk->getRVA() : 0;
1111+
applyArm64Addr(buf + 8, exitThunkRVA, rva + 8, 12);
1112+
applyArm64Imm(buf + 12, exitThunkRVA & 0xfff, 0);
1113+
1114+
Defined *helper = cast<Defined>(file->ctx.config.arm64ECIcallHelper);
1115+
applyArm64Branch26(buf + 16, helper->getRVA() - rva - 16);
1116+
}
1117+
10961118
} // namespace lld::coff

lld/COFF/Chunks.h

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,14 @@ static const uint8_t importThunkARM64[] = {
544544
0x00, 0x02, 0x1f, 0xd6, // br x16
545545
};
546546

547+
static const uint32_t importThunkARM64EC[] = {
548+
0x9000000b, // adrp x11, 0x0
549+
0xf940016b, // ldr x11, [x11]
550+
0x9000000a, // adrp x10, 0x0
551+
0x9100014a, // add x10, x10, #0x0
552+
0x14000000 // b 0x0
553+
};
554+
547555
// Windows-specific.
548556
// A chunk for DLL import jump table entry. In a final output, its
549557
// contents will be a JMP instruction to some __imp_ symbol.
@@ -599,6 +607,22 @@ class ImportThunkChunkARM64 : public ImportThunkChunk {
599607
MachineTypes getMachine() const override { return ARM64; }
600608
};
601609

610+
// ARM64EC __impchk_* thunk implementation.
611+
// Performs an indirect call to an imported function pointer
612+
// using the __icall_helper_arm64ec helper function.
613+
class ImportThunkChunkARM64EC : public ImportThunkChunk {
614+
public:
615+
explicit ImportThunkChunkARM64EC(ImportFile *file);
616+
size_t getSize() const override { return sizeof(importThunkARM64EC); };
617+
MachineTypes getMachine() const override { return ARM64EC; }
618+
void writeTo(uint8_t *buf) const override;
619+
620+
Defined *exitThunk;
621+
622+
private:
623+
ImportFile *file;
624+
};
625+
602626
class RangeExtensionThunkARM : public NonSectionCodeChunk {
603627
public:
604628
explicit RangeExtensionThunkARM(COFFLinkerContext &ctx, Defined *t)

lld/COFF/Config.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ struct Configuration {
164164
std::set<std::string> delayLoads;
165165
std::map<std::string, int> dllOrder;
166166
Symbol *delayLoadHelper = nullptr;
167+
Symbol *arm64ECIcallHelper = nullptr;
167168

168169
bool saveTemps = false;
169170

lld/COFF/Driver.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,11 @@ void LinkerDriver::createECExportThunks() {
13831383
}
13841384
}
13851385

1386+
void LinkerDriver::pullArm64ECIcallHelper() {
1387+
if (!ctx.config.arm64ECIcallHelper)
1388+
ctx.config.arm64ECIcallHelper = addUndefined("__icall_helper_arm64ec");
1389+
}
1390+
13861391
// In MinGW, if no symbols are chosen to be exported, then all symbols are
13871392
// automatically exported by default. This behavior can be forced by the
13881393
// -export-all-symbols option, so that it happens even when exports are
@@ -2685,7 +2690,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
26852690
if (auto *arg = args.getLastArg(OPT_print_symbol_order))
26862691
config->printSymbolOrder = arg->getValue();
26872692

2688-
ctx.symtab.initializeEntryThunks();
2693+
ctx.symtab.initializeECThunks();
26892694

26902695
// Identify unreferenced COMDAT sections.
26912696
if (config->doGC) {

lld/COFF/Driver.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ class LinkerDriver {
101101

102102
std::unique_ptr<llvm::TarWriter> tar; // for /linkrepro
103103

104+
void pullArm64ECIcallHelper();
105+
104106
private:
105107
// Searches a file from search paths.
106108
std::optional<StringRef> findFileIfNew(StringRef filename);

lld/COFF/InputFiles.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,8 @@ void ObjFile::initializeECThunks() {
190190
ctx.symtab.addEntryThunk(getSymbol(entry->src), getSymbol(entry->dst));
191191
break;
192192
case Arm64ECThunkType::Exit:
193+
ctx.symtab.addExitThunk(getSymbol(entry->src), getSymbol(entry->dst));
194+
break;
193195
case Arm64ECThunkType::GuestExit:
194196
break;
195197
default:
@@ -1088,6 +1090,11 @@ void ImportFile::parse() {
10881090
thunkSym = ctx.symtab.addImportThunk(
10891091
name, impSym, make<ImportThunkChunkX64>(ctx, impSym));
10901092
// FIXME: Add aux IAT symbols.
1093+
1094+
StringRef impChkName = saver().save("__impchk_" + name);
1095+
impchkThunk = make<ImportThunkChunkARM64EC>(this);
1096+
ctx.symtab.addImportThunk(impChkName, impSym, impchkThunk);
1097+
ctx.driver.pullArm64ECIcallHelper();
10911098
}
10921099
}
10931100
}

lld/COFF/InputFiles.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class DefinedImportData;
5656
class DefinedImportThunk;
5757
class DefinedRegular;
5858
class ImportThunkChunk;
59+
class ImportThunkChunkARM64EC;
5960
class SectionChunk;
6061
class Symbol;
6162
class Undefined;
@@ -349,6 +350,7 @@ class ImportFile : public InputFile {
349350

350351
DefinedImportData *impSym = nullptr;
351352
Symbol *thunkSym = nullptr;
353+
ImportThunkChunkARM64EC *impchkThunk = nullptr;
352354
std::string dllName;
353355

354356
private:

lld/COFF/MarkLive.cpp

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,23 @@ void markLive(COFFLinkerContext &ctx) {
4343
worklist.push_back(c);
4444
};
4545

46-
auto addSym = [&](Symbol *b) {
47-
if (auto *sym = dyn_cast<DefinedRegular>(b))
46+
std::function<void(Symbol *)> addSym;
47+
48+
auto addImportFile = [&](ImportFile *file) {
49+
file->live = true;
50+
if (file->impchkThunk && file->impchkThunk->exitThunk)
51+
addSym(file->impchkThunk->exitThunk);
52+
};
53+
54+
addSym = [&](Symbol *b) {
55+
if (auto *sym = dyn_cast<DefinedRegular>(b)) {
4856
enqueue(sym->getChunk());
49-
else if (auto *sym = dyn_cast<DefinedImportData>(b))
50-
sym->file->live = true;
51-
else if (auto *sym = dyn_cast<DefinedImportThunk>(b))
52-
sym->wrappedSym->file->live = sym->wrappedSym->file->thunkLive = true;
57+
} else if (auto *sym = dyn_cast<DefinedImportData>(b)) {
58+
addImportFile(sym->file);
59+
} else if (auto *sym = dyn_cast<DefinedImportThunk>(b)) {
60+
addImportFile(sym->wrappedSym->file);
61+
sym->wrappedSym->file->thunkLive = true;
62+
}
5363
};
5464

5565
// Add GC root chunks.

lld/COFF/SymbolTable.cpp

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -557,7 +557,11 @@ void SymbolTable::addEntryThunk(Symbol *from, Symbol *to) {
557557
entryThunks.push_back({from, to});
558558
}
559559

560-
void SymbolTable::initializeEntryThunks() {
560+
void SymbolTable::addExitThunk(Symbol *from, Symbol *to) {
561+
exitThunks[from] = to;
562+
}
563+
564+
void SymbolTable::initializeECThunks() {
561565
for (auto it : entryThunks) {
562566
auto *to = dyn_cast<Defined>(it.second);
563567
if (!to)
@@ -573,6 +577,16 @@ void SymbolTable::initializeEntryThunks() {
573577
}
574578
from->getChunk()->setEntryThunk(to);
575579
}
580+
581+
for (ImportFile *file : ctx.importFileInstances) {
582+
if (!file->impchkThunk)
583+
continue;
584+
585+
Symbol *sym = exitThunks.lookup(file->thunkSym);
586+
if (!sym)
587+
sym = exitThunks.lookup(file->impSym);
588+
file->impchkThunk->exitThunk = dyn_cast_or_null<Defined>(sym);
589+
}
576590
}
577591

578592
Symbol *SymbolTable::addUndefined(StringRef name, InputFile *f,

lld/COFF/SymbolTable.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ class SymbolTable {
108108
ImportThunkChunk *chunk);
109109
void addLibcall(StringRef name);
110110
void addEntryThunk(Symbol *from, Symbol *to);
111-
void initializeEntryThunks();
111+
void addExitThunk(Symbol *from, Symbol *to);
112+
void initializeECThunks();
112113

113114
void reportDuplicate(Symbol *existing, InputFile *newFile,
114115
SectionChunk *newSc = nullptr,
@@ -141,6 +142,7 @@ class SymbolTable {
141142
std::unique_ptr<BitcodeCompiler> lto;
142143
bool ltoCompilationDone = false;
143144
std::vector<std::pair<Symbol *, Symbol *>> entryThunks;
145+
llvm::DenseMap<Symbol *, Symbol *> exitThunks;
144146

145147
COFFLinkerContext &ctx;
146148
};

lld/COFF/Writer.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,6 +1248,8 @@ void Writer::appendImportThunks() {
12481248
DefinedImportThunk *thunk = cast<DefinedImportThunk>(file->thunkSym);
12491249
if (file->thunkLive)
12501250
textSec->addChunk(thunk->getChunk());
1251+
if (file->impchkThunk)
1252+
textSec->addChunk(file->impchkThunk);
12511253
}
12521254

12531255
if (!delayIdata.empty()) {

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ __os_arm64x_dispatch_ret:
3030
.xword 0
3131
__os_arm64x_check_call:
3232
.xword 0
33+
.globl __os_arm64x_dispatch_icall
34+
__os_arm64x_dispatch_icall:
3335
__os_arm64x_check_icall:
3436
.xword 0
3537
__os_arm64x_get_x64_information:

lld/test/COFF/arm64ec-import.test

Lines changed: 74 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,50 +2,73 @@ REQUIRES: aarch64, x86
22
RUN: split-file %s %t.dir && cd %t.dir
33

44
RUN: llvm-mc -filetype=obj -triple=arm64ec-windows test.s -o test.obj
5+
RUN: llvm-mc -filetype=obj -triple=arm64ec-windows icall.s -o icall.obj
6+
RUN: llvm-mc -filetype=obj -triple=arm64ec-windows hybmp.s -o hybmp.obj
57
RUN: llvm-mc -filetype=obj -triple=arm64ec-windows %S/Inputs/loadconfig-arm64ec.s -o loadconfig-arm64ec.obj
68
RUN: llvm-lib -machine:arm64ec -def:test.def -out:test-arm64ec.lib
79
RUN: llvm-lib -machine:arm64ec -def:test2.def -out:test2-arm64ec.lib
810
RUN: llvm-lib -machine:x64 -def:test.def -out:test-x86_64.lib
911

1012
Link using ARM64EC import library:
11-
RUN: lld-link -machine:arm64ec -dll -noentry -out:out.dll loadconfig-arm64ec.obj \
13+
RUN: lld-link -machine:arm64ec -dll -noentry -out:out.dll loadconfig-arm64ec.obj icall.obj hybmp.obj \
1214
RUN: test.obj test-arm64ec.lib test2-arm64ec.lib
1315

1416
Link using x86_64 import library:
15-
RUN: lld-link -machine:arm64ec -dll -noentry -out:out2.dll loadconfig-arm64ec.obj \
17+
RUN: lld-link -machine:arm64ec -dll -noentry -out:out2.dll loadconfig-arm64ec.obj icall.obj hybmp.obj \
1618
RUN: test.obj test-x86_64.lib test2-arm64ec.lib
1719

1820
RUN: llvm-readobj --coff-imports out.dll | FileCheck --check-prefix=IMPORTS %s
1921
RUN: llvm-readobj --coff-imports out2.dll | FileCheck --check-prefix=IMPORTS %s
2022
IMPORTS: Import {
2123
IMPORTS-NEXT: Name: test.dll
2224
IMPORTS-NEXT: ImportLookupTableRVA:
23-
IMPORTS-NEXT: ImportAddressTableRVA: 0x2000
25+
IMPORTS-NEXT: ImportAddressTableRVA: 0x3000
2426
IMPORTS-NEXT: Symbol: data (0)
2527
IMPORTS-NEXT: Symbol: func (0)
2628
IMPORTS-NEXT: Symbol: func2 (0)
2729
IMPORTS-NEXT: }
2830
IMPORTS-NEXT: Import {
2931
IMPORTS-NEXT: Name: test2.dll
3032
IMPORTS-NEXT: ImportLookupTableRVA:
31-
IMPORTS-NEXT: ImportAddressTableRVA: 0x2020
33+
IMPORTS-NEXT: ImportAddressTableRVA: 0x3020
3234
IMPORTS-NEXT: Symbol: t2func (0)
3335
IMPORTS-NEXT: }
3436

3537
RUN: llvm-objdump -d out.dll | FileCheck --check-prefix=DISASM %s
3638
RUN: llvm-objdump -d out2.dll | FileCheck --check-prefix=DISASM %s
3739

38-
DISASM: 0000000180001000 <.text>:
39-
DISASM-NEXT: 180001000: ff 25 02 10 00 00 jmpq *0x1002(%rip) # 0x180002008
40+
DISASM: 180001000: 52800000 mov w0, #0x0 // =0
41+
DISASM-NEXT: 180001004: d65f03c0 ret
42+
DISASM-NEXT: 180001008: d000000b adrp x11, 0x180003000
43+
DISASM-NEXT: 18000100c: f940056b ldr x11, [x11, #0x8]
44+
DISASM-NEXT: 180001010: 9000000a adrp x10, 0x180001000 <.text>
45+
DISASM-NEXT: 180001014: 9101114a add x10, x10, #0x44
46+
DISASM-NEXT: 180001018: 17fffffa b 0x180001000 <.text>
47+
DISASM-NEXT: 18000101c: d000000b adrp x11, 0x180003000
48+
DISASM-NEXT: 180001020: f940096b ldr x11, [x11, #0x10]
49+
DISASM-NEXT: 180001024: f0ffffea adrp x10, 0x180000000
50+
DISASM-NEXT: 180001028: 9100014a add x10, x10, #0x0
51+
DISASM-NEXT: 18000102c: 17fffff5 b 0x180001000 <.text>
52+
DISASM-NEXT: 180001030: d000000b adrp x11, 0x180003000
53+
DISASM-NEXT: 180001034: f940116b ldr x11, [x11, #0x20]
54+
DISASM-NEXT: 180001038: 9000000a adrp x10, 0x180001000 <.text>
55+
DISASM-NEXT: 18000103c: 9101314a add x10, x10, #0x4c
56+
DISASM-NEXT: 180001040: 17fffff0 b 0x180001000 <.text>
57+
DISASM-NEXT: 180001044: 52800020 mov w0, #0x1 // =1
58+
DISASM-NEXT: 180001048: d65f03c0 ret
59+
DISASM-NEXT: 18000104c: 52800040 mov w0, #0x2 // =2
60+
DISASM-NEXT: 180001050: d65f03c0 ret
61+
DISASM-NEXT: ...
62+
DISASM-NEXT: 180002000: ff 25 02 10 00 00 jmpq *0x1002(%rip) # 0x180003008
4063

4164
RUN: llvm-readobj --hex-dump=.test out.dll | FileCheck --check-prefix=TESTSEC %s
4265
RUN: llvm-readobj --hex-dump=.test out2.dll | FileCheck --check-prefix=TESTSEC %s
43-
TESTSEC: 0x180005000 08200000 00200000 10200000 20200000
44-
TESTSEC-NEXT: 0x180005010 00100000
66+
TESTSEC: 0x180006000 08300000 00300000 10300000 20300000
67+
TESTSEC-NEXT: 0x180006010 08100000 1c100000 00200000
4568

4669
RUN: llvm-readobj --headers out.dll | FileCheck -check-prefix=HEADERS %s
47-
HEADERS: LoadConfigTableRVA: 0x3008
48-
HEADERS: IATRVA: 0x2000
70+
HEADERS: LoadConfigTableRVA: 0x4010
71+
HEADERS: IATRVA: 0x3000
4972
HEADERS: IATSize: 0x1000
5073

5174
#--- test.s
@@ -57,8 +80,49 @@ arm64ec_data_sym:
5780
.rva __imp_data
5881
.rva __imp_func2
5982
.rva __imp_t2func
83+
.rva __impchk_func
84+
.rva __impchk_func2
6085
.rva func
6186

87+
#--- icall.s
88+
.text
89+
.globl __icall_helper_arm64ec
90+
.p2align 2, 0x0
91+
__icall_helper_arm64ec:
92+
mov w0, #0
93+
ret
94+
95+
#--- hybmp.s
96+
.section .hybmp$x, "yi"
97+
// __imp_func exit thunk is ignored when func is defined as well
98+
.symidx __imp_func
99+
.symidx dead_exit_thunk
100+
.word 4
101+
.symidx func
102+
.symidx func_exit_thunk
103+
.word 4
104+
.symidx __imp_t2func
105+
.symidx t2func_exit_thunk
106+
.word 4
107+
108+
.section .wowthk$aa,"xr",discard,func_exit_thunk
109+
.globl func_exit_thunk
110+
func_exit_thunk:
111+
mov w0, #1
112+
ret
113+
114+
.section .wowthk$aa,"xr",discard,t2func_exit_thunk
115+
.globl t2func_exit_thunk
116+
t2func_exit_thunk:
117+
mov w0, #2
118+
ret
119+
120+
.section .wowthk$aa,"xr",discard,dead_exit_thunk
121+
.globl dead_exit_thunk
122+
dead_exit_thunk:
123+
mov w0, #0xdead
124+
ret
125+
62126
#--- test.def
63127
NAME test.dll
64128
EXPORTS

0 commit comments

Comments
 (0)