[lld] [LLD][COFF] Replace ARM64EC TLS symbols with native symbols when available (PR #212845)
Jacek Caban via llvm-commits
llvm-commits at lists.llvm.org
Fri Jul 31 05:54:20 PDT 2026
https://github.com/cjacek updated https://github.com/llvm/llvm-project/pull/212845
>From 41a33a0e7220826ec319819fe97a5910def2f3e0 Mon Sep 17 00:00:00 2001
From: Jacek Caban <jacek at codeweavers.com>
Date: Tue, 28 Jul 2026 23:13:04 +0200
Subject: [PATCH] [LLD][COFF] Replace ARM64EC TLS directory chunks with native
chunks when available
On ARM64X targets, CRT provides separate TLS directory chunks, expecting the linker
to sort it out. TLS directory uses _tls_start and _tls_end symbols to reference
.tls section. Those symbols use section sorting to ensure that they are emitted
at the start and end of .tls section, but that's not enough when we have two
separate chunks for views: only one of them can really be the first one. Following
MSVC, merge those chunks instead so that both symbol tables point to the same chunk.
Additionally apply the same logic to _tls_used and _tls_index. This allows entire
TLS directory to be shared between EC and native views. To achieve that, CRT
additionally needs to mark each TLS callback with -arm64xsameaddress. This matches
how MSVC linker and libraries work, but it requires EC and native views to use
the same set of TLS callbacks. We may emit separate TLS directories in the future
to make it more robust.
---
lld/COFF/Chunks.h | 3 +-
lld/COFF/Driver.cpp | 27 ++++++++-
lld/test/COFF/arm64x-tls.s | 121 +++++++++++++++++++++++++++++++++++++
3 files changed, 147 insertions(+), 4 deletions(-)
create mode 100644 lld/test/COFF/arm64x-tls.s
diff --git a/lld/COFF/Chunks.h b/lld/COFF/Chunks.h
index fd2948fc31eb1..a0628654b94e4 100644
--- a/lld/COFF/Chunks.h
+++ b/lld/COFF/Chunks.h
@@ -394,11 +394,12 @@ class SectionChunk : public Chunk {
// and this chunk is considered as dead.
SectionChunk *repl;
+ void replace(SectionChunk *other);
+
private:
SectionChunk *assocChildren = nullptr;
// Used for ICF (Identical COMDAT Folding)
- void replace(SectionChunk *other);
uint32_t eqClass[2] = {0, 0};
// Relocations for this section. Size is stored below.
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index 4eedefbf2ba5e..9d39aff73601b 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -2805,9 +2805,11 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
ltoCompilationDone = true;
ctx.forEachSymtab([](SymbolTable &symtab) { symtab.compileBitcodeFiles(); });
- if (Defined *d =
- dyn_cast_or_null<Defined>(ctx.symtab.findUnderscore("_tls_used")))
- config->gcroot.push_back(d);
+ ctx.forEachSymtab([&](SymbolTable &symtab) {
+ if (Defined *d =
+ dyn_cast_or_null<Defined>(symtab.findUnderscore("_tls_used")))
+ config->gcroot.push_back(d);
+ });
// If -thinlto-index-only is given, we should create only "index
// files" and not object files. Index file creation is already done
@@ -2837,6 +2839,25 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
if (errorCount())
return;
+ if (ctx.hybridSymtab) {
+ // On ARM64X, merge tls chunks, there may be only one true _tls_start and
+ // _tls_end chunk. Additionally, both views use the same _tls_used and
+ // _tls_index.
+ auto maybeReplaceWithNative = [&](StringRef name) {
+ auto nativeSym = dyn_cast_or_null<DefinedRegular>(
+ ctx.hybridSymtab->findUnderscore(name));
+ if (!nativeSym)
+ return;
+ if (auto ecSym =
+ dyn_cast_or_null<DefinedRegular>(ctx.symtab.findUnderscore(name)))
+ nativeSym->getChunk()->replace(ecSym->getChunk());
+ };
+ maybeReplaceWithNative("_tls_used");
+ maybeReplaceWithNative("_tls_index");
+ maybeReplaceWithNative("_tls_start");
+ maybeReplaceWithNative("_tls_end");
+ }
+
ctx.forEachActiveSymtab([](SymbolTable &symtab) {
symtab.initializeECThunks();
symtab.initializeLoadConfig();
diff --git a/lld/test/COFF/arm64x-tls.s b/lld/test/COFF/arm64x-tls.s
new file mode 100644
index 0000000000000..608604fab2fb3
--- /dev/null
+++ b/lld/test/COFF/arm64x-tls.s
@@ -0,0 +1,121 @@
+// REQUIRES: aarch64
+// RUN: split-file %s %t.dir && cd %t.dir
+
+// RUN: llvm-mc -filetype=obj -triple=aarch64-windows aarch64.s -o aarch64.obj
+// RUN: llvm-mc -filetype=obj -triple=arm64ec-windows arm64ec.s -o arm64ec.obj
+// RUN: llvm-mc -filetype=obj -triple=aarch64-windows tls-aarch64.s -o tls-aarch64.obj
+// RUN: llvm-mc -filetype=obj -triple=arm64ec-windows tls-arm64ec.s -o tls-arm64ec.obj
+// RUN: llvm-mc -filetype=obj -triple=arm64ec-windows %S/Inputs/loadconfig-arm64ec.s -o loadconfig-arm64ec.obj
+// RUN: llvm-mc -filetype=obj -triple=aarch64-windows %S/Inputs/loadconfig-arm64.s -o loadconfig-arm64.obj
+
+// RUN: rm -f tls.lib
+// RUN: llvm-lib -machine:arm64x -out:tls.lib tls-aarch64.obj tls-arm64ec.obj loadconfig-arm64ec.obj loadconfig-arm64.obj
+// RUN: lld-link -machine:arm64x -dll -noentry aarch64.obj arm64ec.obj tls.lib -out:out.dll
+
+// Check that we're using native _tls_index and _tls_used for both views.
+
+// RUN: llvm-readobj --coff-tls-directory --hex-dump=.ec --hex-dump=.a64 out.dll | FileCheck %s
+// CHECK: Format: COFF-ARM64X
+// CHECK-NEXT: Arch: aarch64
+// CHECK-NEXT: AddressSize: 64bit
+// CHECK-EMPTY:
+// CHECK-NEXT: Hex dump of section '.a64':
+// CHECK-NEXT: 0x180004000 00500000 04500000 00700000 01700000
+// CHECK-EMPTY:
+// CHECK-NEXT: Hex dump of section '.ec':
+// CHECK-NEXT: 0x180006000 00500000 04500000 00700000 01700000
+// CHECK-NEXT: TLSDirectory {
+// CHECK-NEXT: StartAddressOfRawData: 0x180007000
+// CHECK-NEXT: EndAddressOfRawData: 0x180007001
+// CHECK-NEXT: AddressOfIndex: 0x180005000
+// CHECK-NEXT: AddressOfCallBacks: 0x0
+// CHECK-NEXT: SizeOfZeroFill: 0x0
+// CHECK-NEXT: Characteristics [ (0x100000)
+// CHECK-NEXT: IMAGE_SCN_ALIGN_1BYTES (0x100000)
+// CHECK-NEXT: ]
+// CHECK-NEXT: }
+// CHECK-NEXT: HybridObject {
+// CHECK-NEXT: Format: COFF-ARM64EC
+// CHECK-NEXT: Arch: aarch64
+// CHECK-NEXT: AddressSize: 64bit
+// CHECK-EMPTY:
+// CHECK-NEXT: Hex dump of section '.a64':
+// CHECK-NEXT: 0x180004000 00500000 04500000 00700000 01700000
+// CHECK-EMPTY:
+// CHECK-NEXT: Hex dump of section '.ec':
+// CHECK-NEXT: 0x180006000 00500000 04500000 00700000 01700000
+// CHECK-NEXT: TLSDirectory {
+// CHECK-NEXT: StartAddressOfRawData: 0x180007000
+// CHECK-NEXT: EndAddressOfRawData: 0x180007001
+// CHECK-NEXT: AddressOfIndex: 0x180005000
+// CHECK-NEXT: AddressOfCallBacks: 0x0
+// CHECK-NEXT: SizeOfZeroFill: 0x0
+// CHECK-NEXT: Characteristics [ (0x100000)
+// CHECK-NEXT: IMAGE_SCN_ALIGN_1BYTES (0x100000)
+// CHECK-NEXT: ]
+// CHECK-NEXT: }
+// CHECK-NEXT: }
+
+#--- tls-aarch64.s
+ .section .defa64,"dr",discard,_tls_index
+ .globl _tls_index
+_tls_index:
+ .long 0
+
+ .section .tls,"dr",discard,_tls_start
+ .globl _tls_start
+_tls_start:
+ .byte 0
+
+ .section .tls$ZZZ,"dr",discard,_tls_end
+ .globl _tls_end
+_tls_end:
+ .byte 0
+
+ .section .defa64,"dr",discard,_tls_used
+ .globl _tls_used
+_tls_used:
+ .xword _tls_start
+ .xword _tls_end
+ .xword _tls_index
+ .xword 0
+ .xword 0
+
+#--- aarch64.s
+ .section .a64,"dr"
+ .rva _tls_index
+ .rva _tls_used
+ .rva _tls_start
+ .rva _tls_end
+
+#--- tls-arm64ec.s
+ .section .defec,"dr",discard,_tls_index
+ .globl _tls_index
+_tls_index:
+ .long 0
+
+ .section .tls,"dr",discard,_tls_start
+ .globl _tls_start
+_tls_start:
+ .byte 0
+
+ .section .tls$ZZZ,"dr",discard,_tls_end
+ .globl _tls_end
+_tls_end:
+ .byte 0
+
+ .section .defec,"dr",discard,_tls_used
+ .globl _tls_used
+_tls_used:
+ .xword _tls_start
+ .xword _tls_end
+ .xword _tls_index
+ .xword 0
+ .xword 0
+
+#--- arm64ec.s
+ .section .ec,"dr"
+ .rva _tls_index
+ .rva _tls_used
+ .rva _tls_start
+ .rva _tls_end
More information about the llvm-commits
mailing list