Skip to content

Commit a2d8743

Browse files
authored
[LLD][COFF] Generate X64 thunks for ARM64EC entry points and patchable functions. (#105499)
This implements Fast-Forward Sequences documented in ARM64EC ABI https://learn.microsoft.com/en-us/windows/arm/arm64ec-abi. There are two conditions when linker should generate such thunks: - For each exported ARM64EC functions. It applies only to ARM64EC functions (we may also have pure x64 functions, for which no thunk is needed). MSVC linker creates `EXP+<mangled export name>` symbol in those cases that points to the thunk and uses that symbol for the export. It's observable from the module: it's possible to reference such symbols as I did in the test. Note that it uses export name, not name of the symbol that's exported (as in `foo` in `/EXPORT:foo=bar`). This implies that if the same function is exported multiple times, it will have multiple thunks. I followed this MSVC behavior. - For hybrid_patchable functions. The linker tries to generate a thunk for each undefined `EXP+*` symbol (and such symbols are created by the compiler as a target of weak alias from the demangled name). MSVC linker tries to find corresponding `*$hp_target` symbol and if fails to do so, it outputs a cryptic error like `LINK : fatal error LNK1000: Internal error during IMAGE::BuildImage`. I just skip generating the thunk in such case (which causes undefined reference error). MSVC linker additionally checks that the symbol complex type is a function (see also #102898). We generally don't do such checks in LLD, so I made it less strict. It should be fine: if it's some data symbol, it will not have `$hp_target` symbol, so we will skip it anyway.
1 parent e454d31 commit a2d8743

10 files changed

+395
-6
lines changed

lld/COFF/Chunks.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1073,4 +1073,9 @@ void AbsolutePointerChunk::writeTo(uint8_t *buf) const {
10731073
}
10741074
}
10751075

1076+
void ECExportThunkChunk::writeTo(uint8_t *buf) const {
1077+
memcpy(buf, ECExportThunkCode, sizeof(ECExportThunkCode));
1078+
write32le(buf + 10, target->getRVA() - rva - 14);
1079+
}
1080+
10761081
} // namespace lld::coff

lld/COFF/Chunks.h

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,26 @@ class ECCodeMapChunk : public NonSectionChunk {
749749
std::vector<ECCodeMapEntry> &map;
750750
};
751751

752+
static const uint8_t ECExportThunkCode[] = {
753+
0x48, 0x8b, 0xc4, // movq %rsp, %rax
754+
0x48, 0x89, 0x58, 0x20, // movq %rbx, 0x20(%rax)
755+
0x55, // pushq %rbp
756+
0x5d, // popq %rbp
757+
0xe9, 0, 0, 0, 0, // jmp *0x0
758+
0xcc, // int3
759+
0xcc // int3
760+
};
761+
762+
class ECExportThunkChunk : public NonSectionCodeChunk {
763+
public:
764+
explicit ECExportThunkChunk(Defined *targetSym) : target(targetSym) {}
765+
size_t getSize() const override { return sizeof(ECExportThunkCode); };
766+
void writeTo(uint8_t *buf) const override;
767+
MachineTypes getMachine() const override { return AMD64; }
768+
769+
Defined *target;
770+
};
771+
752772
// MinGW specific, for the "automatic import of variables from DLLs" feature.
753773
// This provides the table of runtime pseudo relocations, for variable
754774
// references that turned out to need to be imported from a DLL even though

lld/COFF/Driver.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,72 @@ void LinkerDriver::convertResources() {
13171317
f->includeResourceChunks();
13181318
}
13191319

1320+
void LinkerDriver::maybeCreateECExportThunk(StringRef name, Symbol *&sym) {
1321+
Defined *def;
1322+
if (!sym)
1323+
return;
1324+
if (auto undef = dyn_cast<Undefined>(sym))
1325+
def = undef->getWeakAlias();
1326+
else
1327+
def = dyn_cast<Defined>(sym);
1328+
if (!def)
1329+
return;
1330+
1331+
if (def->getChunk()->getArm64ECRangeType() != chpe_range_type::Arm64EC)
1332+
return;
1333+
StringRef expName;
1334+
if (auto mangledName = getArm64ECMangledFunctionName(name))
1335+
expName = saver().save("EXP+" + *mangledName);
1336+
else
1337+
expName = saver().save("EXP+" + name);
1338+
sym = addUndefined(expName);
1339+
if (auto undef = dyn_cast<Undefined>(sym)) {
1340+
if (!undef->getWeakAlias()) {
1341+
auto thunk = make<ECExportThunkChunk>(def);
1342+
replaceSymbol<DefinedSynthetic>(undef, undef->getName(), thunk);
1343+
}
1344+
}
1345+
}
1346+
1347+
void LinkerDriver::createECExportThunks() {
1348+
// Check if EXP+ symbols have corresponding $hp_target symbols and use them
1349+
// to create export thunks when available.
1350+
for (Symbol *s : ctx.symtab.expSymbols) {
1351+
if (!s->isUsedInRegularObj)
1352+
continue;
1353+
assert(s->getName().starts_with("EXP+"));
1354+
std::string targetName =
1355+
(s->getName().substr(strlen("EXP+")) + "$hp_target").str();
1356+
Symbol *sym = ctx.symtab.find(targetName);
1357+
if (!sym)
1358+
continue;
1359+
Defined *targetSym;
1360+
if (auto undef = dyn_cast<Undefined>(sym))
1361+
targetSym = undef->getWeakAlias();
1362+
else
1363+
targetSym = dyn_cast<Defined>(sym);
1364+
if (!targetSym)
1365+
continue;
1366+
1367+
auto *undef = dyn_cast<Undefined>(s);
1368+
if (undef && !undef->getWeakAlias()) {
1369+
auto thunk = make<ECExportThunkChunk>(targetSym);
1370+
replaceSymbol<DefinedSynthetic>(undef, undef->getName(), thunk);
1371+
}
1372+
if (!targetSym->isGCRoot) {
1373+
targetSym->isGCRoot = true;
1374+
ctx.config.gcroot.push_back(targetSym);
1375+
}
1376+
}
1377+
1378+
if (ctx.config.entry)
1379+
maybeCreateECExportThunk(ctx.config.entry->getName(), ctx.config.entry);
1380+
for (Export &e : ctx.config.exports) {
1381+
if (!e.data)
1382+
maybeCreateECExportThunk(e.extName.empty() ? e.name : e.extName, e.sym);
1383+
}
1384+
}
1385+
13201386
// In MinGW, if no symbols are chosen to be exported, then all symbols are
13211387
// automatically exported by default. This behavior can be forced by the
13221388
// -export-all-symbols option, so that it happens even when exports are
@@ -2520,6 +2586,9 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
25202586
if (!wrapped.empty())
25212587
wrapSymbols(ctx, wrapped);
25222588

2589+
if (isArm64EC(config->machine))
2590+
createECExportThunks();
2591+
25232592
// Resolve remaining undefined symbols and warn about imported locals.
25242593
ctx.symtab.resolveRemainingUndefines();
25252594
if (errorCount())

lld/COFF/Driver.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,10 @@ class LinkerDriver {
270270
// Convert Windows resource files (.res files) to a .obj file.
271271
MemoryBufferRef convertResToCOFF(ArrayRef<MemoryBufferRef> mbs,
272272
ArrayRef<ObjFile *> objs);
273+
274+
// Create export thunks for exported and patchable Arm64EC function symbols.
275+
void createECExportThunks();
276+
void maybeCreateECExportThunk(StringRef name, Symbol *&sym);
273277
};
274278

275279
// Create enum with OPT_xxx values for each option in Options.td

lld/COFF/SymbolTable.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,9 @@ std::pair<Symbol *, bool> SymbolTable::insert(StringRef name) {
551551
sym->pendingArchiveLoad = false;
552552
sym->canInline = true;
553553
inserted = true;
554+
555+
if (isArm64EC(ctx.config.machine) && name.starts_with("EXP+"))
556+
expSymbols.push_back(sym);
554557
}
555558
return {sym, inserted};
556559
}

lld/COFF/SymbolTable.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ class SymbolTable {
116116
// A list of chunks which to be added to .rdata.
117117
std::vector<Chunk *> localImportChunks;
118118

119+
// A list of EC EXP+ symbols.
120+
std::vector<Symbol *> expSymbols;
121+
119122
// Iterates symbols in non-determinstic hash table order.
120123
template <typename T> void forEachSymbol(T callback) {
121124
for (auto &pair : symMap)

lld/COFF/Writer.cpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,7 @@ class Writer {
304304
uint64_t sizeOfHeaders;
305305

306306
OutputSection *textSec;
307+
OutputSection *hexpthkSec;
307308
OutputSection *rdataSec;
308309
OutputSection *buildidSec;
309310
OutputSection *dataSec;
@@ -984,6 +985,8 @@ void Writer::createSections() {
984985

985986
// Try to match the section order used by link.exe.
986987
textSec = createSection(".text", code | r | x);
988+
if (isArm64EC(ctx.config.machine))
989+
hexpthkSec = createSection(".hexpthk", code | r | x);
987990
createSection(".bss", bss | r | w);
988991
rdataSec = createSection(".rdata", data | r);
989992
buildidSec = createSection(".buildid", data | r);
@@ -2046,6 +2049,14 @@ void Writer::maybeAddRVATable(SymbolRVASet tableSymbols, StringRef tableSym,
20462049

20472050
// Create CHPE metadata chunks.
20482051
void Writer::createECChunks() {
2052+
for (Symbol *s : ctx.symtab.expSymbols) {
2053+
auto sym = dyn_cast<Defined>(s);
2054+
if (!sym || !sym->getChunk())
2055+
continue;
2056+
if (auto thunk = dyn_cast<ECExportThunkChunk>(sym->getChunk()))
2057+
hexpthkSec->addChunk(thunk);
2058+
}
2059+
20492060
auto codeMapChunk = make<ECCodeMapChunk>(codeMap);
20502061
rdataSec->addChunk(codeMapChunk);
20512062
Symbol *codeMapSym = ctx.symtab.findUnderscore("__hybrid_code_map");
Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
REQUIRES: aarch64, x86
2+
RUN: split-file %s %t.dir && cd %t.dir
3+
4+
RUN: llvm-mc -filetype=obj -triple=arm64ec-windows arm64ec-data.s -o arm64ec-data.obj
5+
RUN: llvm-mc -filetype=obj -triple=arm64ec-windows arm64ec-func.s -o arm64ec-func.obj
6+
RUN: llvm-mc -filetype=obj -triple=arm64ec-windows antidep-func.s -o antidep-func.obj
7+
RUN: llvm-mc -filetype=obj -triple=arm64ec-windows arm64ec-data-sym.s -o arm64ec-data-sym.obj
8+
RUN: llvm-mc -filetype=obj -triple=x86_64-windows x86_64-func.s -o x86_64-func.obj
9+
RUN: llvm-mc -filetype=obj -triple=arm64ec-windows %S/Inputs/loadconfig-arm64ec.s -o loadconfig-arm64ec.obj
10+
11+
RUN: lld-link -out:exports.dll -machine:arm64ec arm64ec-func.obj x86_64-func.obj loadconfig-arm64ec.obj \
12+
RUN: arm64ec-data.obj -dll -noentry -export:arm64ec_func -export:func=arm64ec_func \
13+
RUN: -export:x86_64_func -export:data_sym,DATA
14+
15+
RUN: llvm-objdump -d exports.dll | FileCheck -check-prefix=EXP-DISASM %s
16+
EXP-DISASM: Disassembly of section .text:
17+
EXP-DISASM-EMPTY:
18+
EXP-DISASM-NEXT: 0000000180001000 <.text>:
19+
EXP-DISASM-NEXT: 180001000: 90000008 adrp x8, 0x180001000 <.text>
20+
EXP-DISASM-NEXT: 180001004: 52800040 mov w0, #0x2
21+
EXP-DISASM-NEXT: 180001008: d65f03c0 ret
22+
EXP-DISASM-NEXT: ...
23+
EXP-DISASM-EMPTY:
24+
EXP-DISASM-NEXT: 0000000180002000 <x86_64_func>:
25+
EXP-DISASM-NEXT: 180002000: e8 fb ef ff ff callq 0x180001000 <.text>
26+
EXP-DISASM-NEXT: 180002005: b8 03 00 00 00 movl $0x3, %eax
27+
EXP-DISASM-NEXT: 18000200a: c3 retq
28+
EXP-DISASM-EMPTY:
29+
EXP-DISASM-NEXT: Disassembly of section .hexpthk:
30+
EXP-DISASM-EMPTY:
31+
EXP-DISASM-NEXT: 0000000180003000 <func>:
32+
EXP-DISASM-NEXT: 180003000: 48 8b c4 movq %rsp, %rax
33+
EXP-DISASM-NEXT: 180003003: 48 89 58 20 movq %rbx, 0x20(%rax)
34+
EXP-DISASM-NEXT: 180003007: 55 pushq %rbp
35+
EXP-DISASM-NEXT: 180003008: 5d popq %rbp
36+
EXP-DISASM-NEXT: 180003009: e9 f2 df ff ff jmp 0x180001000 <.text>
37+
EXP-DISASM-NEXT: 18000300e: cc int3
38+
EXP-DISASM-NEXT: 18000300f: cc int3
39+
EXP-DISASM-EMPTY:
40+
EXP-DISASM-NEXT: 0000000180003010 <arm64ec_func>:
41+
EXP-DISASM-NEXT: 180003010: 48 8b c4 movq %rsp, %rax
42+
EXP-DISASM-NEXT: 180003013: 48 89 58 20 movq %rbx, 0x20(%rax)
43+
EXP-DISASM-NEXT: 180003017: 55 pushq %rbp
44+
EXP-DISASM-NEXT: 180003018: 5d popq %rbp
45+
EXP-DISASM-NEXT: 180003019: e9 e2 df ff ff jmp 0x180001000 <.text>
46+
EXP-DISASM-NEXT: 18000301e: cc int3
47+
EXP-DISASM-NEXT: 18000301f: cc int3
48+
49+
RUN: llvm-objdump -p exports.dll | FileCheck -check-prefix=EXP-EXPORT %s
50+
EXP-EXPORT: Ordinal RVA Name
51+
EXP-EXPORT-NEXT: 1 0x3010 arm64ec_func
52+
EXP-EXPORT-NEXT: 2 0x6000 data_sym
53+
EXP-EXPORT-NEXT: 3 0x3000 func
54+
EXP-EXPORT-NEXT: 4 0x2000 x86_64_func
55+
56+
RUN: llvm-readobj --coff-load-config exports.dll | FileCheck -check-prefix=EXP-CHPE %s
57+
EXP-CHPE: CodeMap [
58+
EXP-CHPE-NEXT: 0x1000 - 0x100C ARM64EC
59+
EXP-CHPE-NEXT: 0x2000 - 0x3020 X64
60+
EXP-CHPE-NEXT: ]
61+
62+
RUN: llvm-objdump -s --section=.test exports.dll | FileCheck --check-prefix=EXP-DATA %s
63+
EXP-DATA: 180006000 00300000 10300000
64+
65+
RUN: lld-link -out:exports2.dll -machine:arm64ec antidep-func.obj x86_64-func.obj loadconfig-arm64ec.obj \
66+
RUN: arm64ec-data.obj -dll -noentry -export:arm64ec_func -export:func=arm64ec_func \
67+
RUN: -export:x86_64_func -export:data_sym,DATA
68+
69+
RUN: llvm-objdump -d exports2.dll | FileCheck -check-prefix=EXP-DISASM %s
70+
RUN: llvm-objdump -p exports2.dll | FileCheck -check-prefix=EXP-EXPORT %s
71+
RUN: llvm-objdump -s --section=.test exports2.dll | FileCheck --check-prefix=EXP-DATA %s
72+
RUN: llvm-readobj --coff-load-config exports2.dll | FileCheck -check-prefix=EXP-CHPE %s
73+
74+
RUN: lld-link -out:entry.dll -machine:arm64ec arm64ec-func.obj loadconfig-arm64ec.obj -dll -entry:arm64ec_func
75+
76+
RUN: llvm-objdump -d entry.dll | FileCheck -check-prefix=ENTRY-DISASM %s
77+
ENTRY-DISASM: Disassembly of section .text:
78+
ENTRY-DISASM-EMPTY:
79+
ENTRY-DISASM-NEXT: 0000000180001000 <.text>:
80+
ENTRY-DISASM-NEXT: 180001000: 90000008 adrp x8, 0x180001000 <.text>
81+
ENTRY-DISASM-NEXT: 180001004: 52800040 mov w0, #0x2 // =2
82+
ENTRY-DISASM-NEXT: 180001008: d65f03c0 ret
83+
ENTRY-DISASM-EMPTY:
84+
ENTRY-DISASM-NEXT: Disassembly of section .hexpthk:
85+
ENTRY-DISASM-EMPTY:
86+
ENTRY-DISASM-NEXT: 0000000180002000 <.hexpthk>:
87+
ENTRY-DISASM-NEXT: 180002000: 48 8b c4 movq %rsp, %rax
88+
ENTRY-DISASM-NEXT: 180002003: 48 89 58 20 movq %rbx, 0x20(%rax)
89+
ENTRY-DISASM-NEXT: 180002007: 55 pushq %rbp
90+
ENTRY-DISASM-NEXT: 180002008: 5d popq %rbp
91+
ENTRY-DISASM-NEXT: 180002009: e9 f2 ef ff ff jmp 0x180001000 <.text>
92+
ENTRY-DISASM-NEXT: 18000200e: cc int3
93+
ENTRY-DISASM-NEXT: 18000200f: cc int3
94+
95+
RUN: llvm-readobj --headers entry.dll | FileCheck -check-prefix=ENTRY %s
96+
ENTRY: AddressOfEntryPoint: 0x2000
97+
98+
RUN: llvm-readobj --coff-load-config entry.dll | FileCheck -check-prefix=ENTRY-CHPE %s
99+
ENTRY-CHPE: CodeMap [
100+
ENTRY-CHPE-NEXT: 0x1000 - 0x100C ARM64EC
101+
ENTRY-CHPE-NEXT: 0x2000 - 0x2010 X64
102+
ENTRY-CHPE-NEXT: ]
103+
104+
105+
Test exporting data symbol as a function:
106+
107+
RUN: lld-link -out:data-func.dll -machine:arm64ec arm64ec-data-sym.obj loadconfig-arm64ec.obj -dll -noentry -export:data_sym
108+
109+
RUN: llvm-readobj --hex-dump=.test data-func.dll | FileCheck --check-prefix=DATAFUNC-TEST %s
110+
DATAFUNC-TEST: Hex dump of section '.test':
111+
DATAFUNC-TEST-NEXT: 0x180003000 00000000 ....
112+
113+
RUN: llvm-readobj --coff-exports --hex-dump=.test data-func.dll | FileCheck --check-prefix=DATAFUNC-EXP %s
114+
DATAFUNC-EXP: Export {
115+
DATAFUNC-EXP-NEXT: Ordinal: 1
116+
DATAFUNC-EXP-NEXT: Name: data_sym
117+
DATAFUNC-EXP-NEXT: RVA: 0x3000
118+
DATAFUNC-EXP-NEXT: }
119+
120+
121+
Test mingw-style auto-export:
122+
123+
RUN: lld-link -out:export-all.dll -machine:arm64ec arm64ec-func.obj loadconfig-arm64ec.obj -dll -noentry -lldmingw
124+
RUN: llvm-objdump -d export-all.dll | FileCheck --check-prefix=EXPORT-ALL %s
125+
126+
EXPORT-ALL: Disassembly of section .text:
127+
EXPORT-ALL-EMPTY:
128+
EXPORT-ALL-NEXT: 0000000180001000 <.text>:
129+
EXPORT-ALL-NEXT: 180001000: 90000008 adrp x8, 0x180001000 <.text>
130+
EXPORT-ALL-NEXT: 180001004: 52800040 mov w0, #0x2 // =2
131+
EXPORT-ALL-NEXT: 180001008: d65f03c0 ret
132+
EXPORT-ALL-EMPTY:
133+
EXPORT-ALL-NEXT: Disassembly of section .hexpthk:
134+
EXPORT-ALL-EMPTY:
135+
EXPORT-ALL-NEXT: 0000000180002000 <arm64ec_func>:
136+
EXPORT-ALL-NEXT: 180002000: 48 8b c4 movq %rsp, %rax
137+
EXPORT-ALL-NEXT: 180002003: 48 89 58 20 movq %rbx, 0x20(%rax)
138+
EXPORT-ALL-NEXT: 180002007: 55 pushq %rbp
139+
EXPORT-ALL-NEXT: 180002008: 5d popq %rbp
140+
EXPORT-ALL-NEXT: 180002009: e9 f2 ef ff ff jmp 0x180001000 <.text>
141+
EXPORT-ALL-NEXT: 18000200e: cc int3
142+
EXPORT-ALL-NEXT: 18000200f: cc int3
143+
144+
145+
#--- arm64ec-func.s
146+
.text
147+
.globl arm64ec_func
148+
.p2align 2, 0x0
149+
arm64ec_func:
150+
adrp x8,arm64ec_func
151+
mov w0, #2
152+
ret
153+
154+
#--- antidep-func.s
155+
.text
156+
.globl "#arm64ec_func"
157+
.p2align 2, 0x0
158+
"#arm64ec_func":
159+
adrp x8,arm64ec_func
160+
mov w0, #2
161+
ret
162+
163+
.weak_anti_dep arm64ec_func
164+
arm64ec_func = "#arm64ec_func"
165+
166+
#--- arm64ec-data.s
167+
.section .test, "r"
168+
.globl data_sym
169+
.p2align 2, 0x0
170+
data_sym:
171+
.rva "EXP+#func"
172+
.rva "EXP+#arm64ec_func"
173+
174+
#--- x86_64-func.s
175+
.text
176+
.globl x86_64_func
177+
.p2align 2, 0x0
178+
x86_64_func:
179+
call arm64ec_func
180+
movl $3, %eax
181+
retq
182+
183+
#--- arm64ec-data-sym.s
184+
.section .test, "r"
185+
.globl data_sym
186+
.p2align 2, 0x0
187+
data_sym:
188+
.word 0

0 commit comments

Comments
 (0)