[lld] e096d2f - [LLD][COFF] Replace ARM64EC TLS directory chunks with native chunks when available (#212845)
via llvm-commits
llvm-commits at lists.llvm.org
Sun Aug 2 03:39:48 PDT 2026
Author: Jacek Caban
Date: 2026-08-02T12:39:43+02:00
New Revision: e096d2f60dbc6cab991d5c02a5f7125a6dc694dc
URL: https://github.com/llvm/llvm-project/commit/e096d2f60dbc6cab991d5c02a5f7125a6dc694dc
DIFF: https://github.com/llvm/llvm-project/commit/e096d2f60dbc6cab991d5c02a5f7125a6dc694dc.diff
LOG: [LLD][COFF] Replace ARM64EC TLS directory chunks with native chunks when available (#212845)
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.
Added:
lld/test/COFF/arm64x-tls.s
Modified:
lld/COFF/Chunks.h
lld/COFF/Driver.cpp
Removed:
################################################################################
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