[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