[lld] f1da2eb - [ELF] Handle and optimize x86-64 PLTOFF64 TLS sequences (#216263)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 18 10:20:08 PDT 2026
Author: Fangrui Song
Date: 2026-08-18T10:20:02-07:00
New Revision: f1da2ebea2dba0a3f4d7fb7e84dc978a55b1e3af
URL: https://github.com/llvm/llvm-project/commit/f1da2ebea2dba0a3f4d7fb7e84dc978a55b1e3af
DIFF: https://github.com/llvm/llvm-project/commit/f1da2ebea2dba0a3f4d7fb7e84dc978a55b1e3af.diff
LOG: [ELF] Handle and optimize x86-64 PLTOFF64 TLS sequences (#216263)
In `gcc -mcmodel=large` generated General Dynamic/Local Dynamic TLS
sequences, R_X86_64_TLSGD/R_X86_64_TLSLD is followed by a MOVABS loading
`__tls_get_addr at pltoff`, and the call goes through a register:
```
leaq x at tlsgd(%rip), %rdi # R_X86_64_TLSGD
movabsq $__tls_get_addr at pltoff, %rax # R_X86_64_PLTOFF64
addq %rbx, %rax
callq *%rax
```
`relaxTlsGdToLe` and `relaxTlsGdToIe` write the 16-byte direct call
sequence at
loc-4, corrupting the preceding instruction, while `relaxTlsLdToLe`
reports
"expected R_X86_64_PLT32 or R_X86_64_GOTPCRELX after R_X86_64_TLSLD".
Detect the MOVABS and rewrite the 22 bytes, matching GNU ld.
`__tls_get_addr` doesn't need to be defined when optimized to LE/IE,
LLM-aided
Added:
lld/test/ELF/x86-64-tls-pltoff64.s
Modified:
lld/ELF/Arch/X86_64.cpp
Removed:
################################################################################
diff --git a/lld/ELF/Arch/X86_64.cpp b/lld/ELF/Arch/X86_64.cpp
index fee33b03ffa6a..f172080dc2b6a 100644
--- a/lld/ELF/Arch/X86_64.cpp
+++ b/lld/ELF/Arch/X86_64.cpp
@@ -778,9 +778,32 @@ void X86_64::scanSection(InputSectionBase &sec, unsigned shard) {
elf::scanSection1<X86_64, ELF32LE>(*this, sec, shard);
}
+static bool isPltOff64Tls(const uint8_t *loc) {
+ return loc[4] == 0x48 && loc[5] == 0xb8;
+}
+
void X86_64::relaxTlsGdToLe(uint8_t *loc, const Relocation &rel,
uint64_t val) const {
if (rel.type == R_X86_64_TLSGD) {
+ // TLSGD can be directly followed by MOVABS (instead of CALL):
+ // leaq x at tlsgd(%rip), %rdi # 48 8d 3d <disp32>, TLSGD
+ // movabsq $__tls_get_addr at pltoff, %rax # 48 b8 <imm64>, PLTOFF64
+ // addq %REG, %rax
+ // callq *%rax
+ if (isPltOff64Tls(loc)) {
+ // Convert to the following three instructions.
+ const uint8_t inst[] = {
+ 0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00,
+ 0x00, 0x00, // mov %fs:0x0,%rax
+ 0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea x at tpoff,%rax
+ 0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // 6-byte nop
+ };
+ memcpy(loc - 3, inst, sizeof(inst));
+ // The original code used a pc relative relocation and so we have to
+ // compensate for the -4 in had in the addend.
+ write32le(loc + 9, val + 4);
+ return;
+ }
// Convert
// .byte 0x66
// leaq x at tlsgd(%rip), %rdi
@@ -794,9 +817,6 @@ void X86_64::relaxTlsGdToLe(uint8_t *loc, const Relocation &rel,
0x48, 0x8d, 0x80, 0, 0, 0, 0, // lea x at tpoff,%rax
};
memcpy(loc - 4, inst, sizeof(inst));
-
- // The original code used a pc relative relocation and so we have to
- // compensate for the -4 in had in the addend.
write32le(loc + 8, val + 4);
} else if (rel.type == R_X86_64_GOTPC32_TLSDESC ||
rel.type == R_X86_64_CODE_4_GOTPC32_TLSDESC) {
@@ -830,6 +850,25 @@ void X86_64::relaxTlsGdToLe(uint8_t *loc, const Relocation &rel,
void X86_64::relaxTlsGdToIe(uint8_t *loc, const Relocation &rel,
uint64_t val) const {
if (rel.type == R_X86_64_TLSGD) {
+ if (isPltOff64Tls(loc)) {
+ // Convert
+ // leaq x at tlsgd(%rip), %rdi # 48 8d 3d <disp32>, TLSLD
+ // movabsq $__tls_get_addr at pltoff, %rax # 48 b8 <imm64>, PLTOFF64
+ // addq %REG, %rax
+ // callq *%rax
+ // to the following three instructions.
+ const uint8_t inst[] = {
+ 0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00,
+ 0x00, 0x00, // mov %fs:0x0,%rax
+ 0x48, 0x03, 0x05, 0, 0, 0, 0, // addq x at gottpoff(%rip),%rax
+ 0x66, 0x0f, 0x1f, 0x44, 0x00, 0x00, // nopw 0x0(%rax,%rax,1)
+ };
+ memcpy(loc - 3, inst, sizeof(inst));
+ // Both code sequences are PC relatives, but since we are moving the
+ // constant forward by 9 bytes we have to subtract the value by 9.
+ write32le(loc + 9, val - 9);
+ return;
+ }
// Convert
// .byte 0x66
// leaq x at tlsgd(%rip), %rdi
@@ -1006,6 +1045,24 @@ void X86_64::relaxTlsLdToLe(uint8_t *loc, const Relocation &rel,
return;
}
+ if (isPltOff64Tls(loc)) {
+ // Convert
+ // leaq x at tlsld(%rip), %rdi
+ // movabsq $__tls_get_addr at pltoff, %rax
+ // addq %REG, %rax
+ // callq *%rax
+ // to
+ // data16 data16 data16 cs nopw 0x0(%rax,%rax,1)
+ // movq %fs:0,%rax
+ const uint8_t inst[] = {
+ 0x66, 0x66, 0x66, 0x66, 0x2e, 0x0f, 0x1f, 0x84, 0x00,
+ 0x00, 0x00, 0x00, 0x00, // 13-byte nop
+ 0x64, 0x48, 0x8b, 0x04, 0x25, 0x00, 0x00, 0x00, 0x00, // movq %fs:0,%rax
+ };
+ memcpy(loc - 3, inst, sizeof(inst));
+ return;
+ }
+
ErrAlways(ctx)
<< getErrorLoc(ctx, loc - 3)
<< "expected R_X86_64_PLT32 or R_X86_64_GOTPCRELX after R_X86_64_TLSLD";
diff --git a/lld/test/ELF/x86-64-tls-pltoff64.s b/lld/test/ELF/x86-64-tls-pltoff64.s
new file mode 100644
index 0000000000000..9c2aef215d147
--- /dev/null
+++ b/lld/test/ELF/x86-64-tls-pltoff64.s
@@ -0,0 +1,98 @@
+# REQUIRES: x86
+## Test dynamic TLS sequences that call __tls_get_addr indirectly through
+## R_X86_64_PLTOFF64 instead of a direct call.
+
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64 a.s -o a.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 b.s -o b.o
+# RUN: ld.lld -shared b.o -soname=b.so -o b.so
+
+# RUN: ld.lld a.o b.so -o out
+# RUN: llvm-readelf -Sr out | FileCheck %s --check-prefix=SEC
+# RUN: llvm-objdump -d --no-show-raw-insn --no-print-imm-hex out | FileCheck %s --check-prefix=EXE
+
+# RUN: ld.lld -shared a.o b.so -o out.so
+# RUN: llvm-readelf -r out.so | FileCheck %s --check-prefix=SDYN
+# RUN: llvm-objdump -d --no-show-raw-insn --no-print-imm-hex out.so | FileCheck %s --check-prefix=SHARED
+
+# SEC: .got PROGBITS 00000000002023c8
+# SEC: Relocation section '.rela.dyn' {{.*}} contains 1 entries:
+# SEC-NEXT: Offset
+# SEC-NEXT: 00000000002023c8 {{.*}} R_X86_64_TPOFF64 {{.*}} y + 0
+
+## The TLS block is 15 bytes. x1 is at DTPOFF 7 and TPOFF -8, and x2 at DTPOFF 11
+## and TPOFF -4. Each optimized sequence is padded with a nop to the original 22 bytes.
+# EXE-LABEL: <_start>:
+# EXE: movq %fs:0, %rax
+# EXE-NEXT: leaq -8(%rax), %rax
+# EXE-NEXT: nopw (%rax,%rax)
+
+## y is preemptible. Its GD sequence is optimized to IE.
+# EXE-NEXT: movq %fs:0, %rax
+# EXE-NEXT: addq [[#]](%rip), %rax # 0x2023c8
+# EXE-NEXT: nopw (%rax,%rax)
+
+# EXE-NEXT: nopw %cs:(%rax,%rax)
+# EXE-NEXT: movq %fs:0, %rax
+# EXE-NEXT: leaq -8(%rax), %rcx
+# EXE-NEXT: leaq -4(%rax), %rdx
+
+# SDYN: Relocation section '.rela.dyn' {{.*}} contains 4 entries:
+# SDYN: 00000000000024e0 {{.*}} R_X86_64_DTPMOD64 0
+# SDYN-NEXT: 00000000000024f0 {{.*}} R_X86_64_DTPMOD64 0
+# SDYN-NEXT: 0000000000002500 {{.*}} R_X86_64_DTPMOD64 {{.*}} y + 0
+# SDYN-NEXT: 0000000000002508 {{.*}} R_X86_64_DTPOFF64 {{.*}} y + 0
+# SDYN: Relocation section '.rela.plt' {{.*}} contains 1 entries:
+# SDYN: {{.*}} R_X86_64_JUMP_SLOT {{.*}} __tls_get_addr + 0
+
+# SHARED-LABEL: <_start>:
+# SHARED: leaq [[#]](%rip), %rdi # 0x24f0
+# SHARED-NEXT: movabsq $-8496, %rax
+# SHARED-NEXT: addq %rbx, %rax
+# SHARED-NEXT: callq *%rax
+
+# SHARED-NEXT: leaq [[#]](%rip), %rdi # 0x2500
+# SHARED-NEXT: movabsq $-8496, %rax
+# SHARED-NEXT: addq %rbx, %rax
+# SHARED-NEXT: callq *%rax
+
+# SHARED-NEXT: leaq [[#]](%rip), %rdi # 0x24e0
+# SHARED-NEXT: movabsq $-8496, %rax
+# SHARED-NEXT: addq %r15, %rax
+# SHARED-NEXT: callq *%rax
+
+## x1 is at DTPOFF 7
+# SHARED-NEXT: leaq 7(%rax), %rcx
+# SHARED-NEXT: leaq 11(%rax), %rdx
+
+#--- a.s
+.globl _start
+_start:
+ leaq x1 at tlsgd(%rip), %rdi
+ movabsq $__tls_get_addr at PLTOFF, %rax
+ addq %rbx, %rax
+ callq *%rax
+
+ leaq y at tlsgd(%rip), %rdi
+ movabsq $__tls_get_addr at PLTOFF, %rax
+ addq %rbx, %rax
+ callq *%rax
+
+ leaq x1 at tlsld(%rip), %rdi
+ movabsq $__tls_get_addr at PLTOFF, %rax
+ addq %r15, %rax
+ callq *%rax
+ leaq x1 at dtpoff(%rax), %rcx
+ leaq x2 at dtpoff(%rax), %rdx
+
+.section .tbss,"awT", at nobits
+.globl x1, x2
+.hidden x1, x2
+.space 7
+x1: .zero 4
+x2: .zero 4
+
+#--- b.s
+.section .tbss,"awT", at nobits
+.globl y
+y: .zero 4
More information about the llvm-commits
mailing list