[lld] [lld][ELF][x86-64] Do not relax large-model TLS sequences (PR #216188)
Farid Zakaria via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 13 14:56:40 PDT 2026
https://github.com/fzakaria created https://github.com/llvm/llvm-project/pull/216188
GCC uses an indirect PLTOFF64 call to __tls_get_addr for large-model TLSGD and TLSLD sequences. These sequences do not match the fixed-size psABI templates that lld rewrites when relaxing TLS to IE or LE.
Detect the PLTOFF64 relocation following TLSGD or TLSLD and preserve the original sequence instead of rewriting unrelated instruction bytes.
Assisted-by: Codex
>From 2f2ac32c2d7300807c089d409ee400a31f9c1919 Mon Sep 17 00:00:00 2001
From: Farid Zakaria <fmzakari at fb.com>
Date: Thu, 13 Aug 2026 14:48:43 -0700
Subject: [PATCH] [lld][ELF][x86-64] Do not relax large-model TLS sequences
GCC uses an indirect PLTOFF64 call to __tls_get_addr for large-model
TLSGD and TLSLD sequences. These sequences do not match the fixed-size
psABI templates that lld rewrites when relaxing TLS to IE or LE.
Detect the PLTOFF64 relocation following TLSGD or TLSLD and preserve the
original sequence instead of rewriting unrelated instruction bytes.
Assisted-by: Codex
---
lld/ELF/Arch/X86_64.cpp | 13 +++++--
lld/ELF/RelocScan.h | 4 +-
lld/test/ELF/x86-64-tls-large-model.s | 55 +++++++++++++++++++++++++++
3 files changed, 67 insertions(+), 5 deletions(-)
create mode 100644 lld/test/ELF/x86-64-tls-large-model.s
diff --git a/lld/ELF/Arch/X86_64.cpp b/lld/ELF/Arch/X86_64.cpp
index fee33b03ffa6a..8ea974cc7cccf 100644
--- a/lld/ELF/Arch/X86_64.cpp
+++ b/lld/ELF/Arch/X86_64.cpp
@@ -659,6 +659,10 @@ void X86_64::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
continue;
int64_t addend = rs.getAddend<ELFT>(rel, type);
RelExpr expr;
+ auto isLargeModelTlsCall = [&] {
+ auto next = std::next(it);
+ return next != rels.end() && next->getType(false) == R_X86_64_PLTOFF64;
+ };
// Relocation types that only need a RelExpr set `expr` and break out of
// the switch to reach rs.process(). Types that need special handling
// (fast-path helpers, TLS) call a handler and use `continue`.
@@ -727,12 +731,15 @@ void X86_64::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
rs.handleTlsIe(R_GOT_PC, type, offset, addend, sym);
continue;
case R_X86_64_TLSGD:
- if (rs.handleTlsGd(R_TLSGD_PC, R_GOT_PC, R_TPREL, type, offset, addend,
- sym))
+ // GCC's large-model sequence uses PLTOFF64 to call __tls_get_addr and
+ // does not match the fixed-size sequence required by TLS relaxation.
+ if (rs.handleTlsGd(R_TLSGD_PC, isLargeModelTlsCall() ? R_NONE : R_GOT_PC,
+ R_TPREL, type, offset, addend, sym))
++it;
continue;
case R_X86_64_TLSLD:
- if (rs.handleTlsLd(R_TLSLD_PC, type, offset, addend, sym))
+ if (rs.handleTlsLd(R_TLSLD_PC, type, offset, addend, sym,
+ !isLargeModelTlsCall()))
++it;
continue;
case R_X86_64_DTPOFF32:
diff --git a/lld/ELF/RelocScan.h b/lld/ELF/RelocScan.h
index c39089840d2fb..fe3bafab8e373 100644
--- a/lld/ELF/RelocScan.h
+++ b/lld/ELF/RelocScan.h
@@ -127,8 +127,8 @@ class RelocScan {
// Handle TLS Local-Dynamic relocation. Returns true if the __tls_get_addr
// call should be skipped (i.e., caller should ++it).
bool handleTlsLd(RelExpr sharedExpr, RelType type, uint64_t offset,
- int64_t addend, Symbol &sym) {
- if (ctx.arg.shared) {
+ int64_t addend, Symbol &sym, bool enableLdToLe = true) {
+ if (ctx.arg.shared || !enableLdToLe) {
ctx.needsTlsLd.store(true, std::memory_order_relaxed);
sec->addReloc({sharedExpr, type, offset, addend, &sym});
return false;
diff --git a/lld/test/ELF/x86-64-tls-large-model.s b/lld/test/ELF/x86-64-tls-large-model.s
new file mode 100644
index 0000000000000..e10765acf46dd
--- /dev/null
+++ b/lld/test/ELF/x86-64-tls-large-model.s
@@ -0,0 +1,55 @@
+# REQUIRES: x86
+
+## GCC uses PLTOFF64 in large-model TLSGD and TLSLD sequences. They do not
+## match the fixed-size sequences that the linker can relax to IE or LE.
+
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o
+# RUN: ld.lld -e _start %t.o -o %t.exe
+# RUN: llvm-objdump --no-print-imm-hex --no-show-raw-insn -d %t.exe | FileCheck %s
+
+# CHECK-LABEL: <_start>:
+# CHECK: leaq {{.*}}(%rip), %rdi
+# CHECK-NEXT: movabsq ${{.*}}, %rax
+# CHECK-NEXT: addq %rbx, %rax
+# CHECK-NEXT: callq *%rax
+# CHECK-NEXT: leaq {{.*}}(%rip), %rdi
+# CHECK-NEXT: movabsq ${{.*}}, %rax
+# CHECK-NEXT: addq %rbx, %rax
+# CHECK-NEXT: callq *%rax
+# CHECK-NEXT: leaq -4(%rax), %rax
+# CHECK-NEXT: retq
+
+.section .ltext,"axl", at progbits
+.globl _start
+.type _start, @function
+_start:
+.Lpic:
+ movabsq $_GLOBAL_OFFSET_TABLE_-.Lpic, %r11
+ leaq .Lpic(%rip), %rbx
+ addq %r11, %rbx
+
+ leaq x at tlsgd(%rip), %rdi
+ movabsq $__tls_get_addr at PLTOFF, %rax
+ addq %rbx, %rax
+ callq *%rax
+
+ leaq x at tlsld(%rip), %rdi
+ movabsq $__tls_get_addr at PLTOFF, %rax
+ addq %rbx, %rax
+ callq *%rax
+ leaq x at dtpoff(%rax), %rax
+ retq
+.size _start, .-_start
+
+.hidden __tls_get_addr
+.globl __tls_get_addr
+.type __tls_get_addr, @function
+__tls_get_addr:
+ retq
+.size __tls_get_addr, .-__tls_get_addr
+
+.section .tbss,"awT", at nobits
+.hidden x
+.globl x
+x:
+ .zero 4
More information about the llvm-commits
mailing list