[llvm] [X86] Use a large-model call for __tls_get_addr (PR #216195)

Farid Zakaria via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 14 10:52:12 PDT 2026


https://github.com/fzakaria updated https://github.com/llvm/llvm-project/pull/216195

>From 870f6eed0149eb0a15c662b4829d8b996415cb36 Mon Sep 17 00:00:00 2001
From: Farid Zakaria <fmzakari at fb.com>
Date: Fri, 14 Aug 2026 10:50:55 -0700
Subject: [PATCH] [X86] Use a large-model call for __tls_get_addr

The x86-64 ELF TLSGD and TLSLD sequences currently use a PLT32 call
to __tls_get_addr for every code model. The call can overflow in the
large code model when the PLT is more than 2 GiB away.

Use the same relocation strategy as GCC for large-model PIC: materialize
the GOT base with GOTPC64, materialize __tls_get_addr with PLTOFF64, and
call it indirectly. This changes only x86-64 LP64 ELF PIC code using
the large code model.

Assisted-by: Codex
---
 llvm/lib/Target/X86/X86MCInstLower.cpp   | 42 ++++++++++++++++++++-
 llvm/test/CodeGen/X86/code-model-elf.ll  | 23 ------------
 llvm/test/CodeGen/X86/tls-large-model.ll | 47 ++++++++++++++++++++++++
 3 files changed, 88 insertions(+), 24 deletions(-)
 create mode 100644 llvm/test/CodeGen/X86/tls-large-model.ll

diff --git a/llvm/lib/Target/X86/X86MCInstLower.cpp b/llvm/lib/Target/X86/X86MCInstLower.cpp
index ad0946b4c3310..87031104294f7 100644
--- a/llvm/lib/Target/X86/X86MCInstLower.cpp
+++ b/llvm/lib/Target/X86/X86MCInstLower.cpp
@@ -595,6 +595,47 @@ void X86AsmPrinter::LowerTlsAddr(X86MCInstLower &MCInstLowering,
             .addReg(0));
   } else if (Is64Bits) {
     bool NeedsPadding = Specifier == X86::S_TLSGD;
+    const MCSymbol *TlsGetAddr = Ctx.getOrCreateSymbol("__tls_get_addr");
+    if (Is64BitsLP64 && getSubtarget().isTargetELF() &&
+        TM.getCodeModel() == CodeModel::Large && TM.isPositionIndependent()) {
+      MCSymbol *PICBase = Ctx.createTempSymbol();
+      OutStreamer->emitLabel(PICBase);
+      EmitAndCountInstruction(
+          MCInstBuilder(X86::LEA64r)
+              .addReg(X86::R11)
+              .addReg(X86::RIP)
+              .addImm(1)
+              .addReg(0)
+              .addExpr(MCSymbolRefExpr::create(PICBase, Ctx))
+              .addReg(0));
+      const MCExpr *GOT = MCSymbolRefExpr::create(
+          Ctx.getOrCreateSymbol("_GLOBAL_OFFSET_TABLE_"), Ctx);
+      const MCExpr *GOTOffset = MCBinaryExpr::createSub(
+          GOT, MCSymbolRefExpr::create(PICBase, Ctx), Ctx);
+      EmitAndCountInstruction(
+          MCInstBuilder(X86::MOV64ri).addReg(X86::RAX).addExpr(GOTOffset));
+      EmitAndCountInstruction(MCInstBuilder(X86::ADD64rr)
+                                  .addReg(X86::R11)
+                                  .addReg(X86::R11)
+                                  .addReg(X86::RAX));
+      EmitAndCountInstruction(MCInstBuilder(X86::LEA64r)
+                                  .addReg(X86::RDI)
+                                  .addReg(X86::RIP)
+                                  .addImm(1)
+                                  .addReg(0)
+                                  .addExpr(Sym)
+                                  .addReg(0));
+      const MCExpr *Callee =
+          MCSymbolRefExpr::create(TlsGetAddr, X86::S_PLTOFF, Ctx);
+      EmitAndCountInstruction(
+          MCInstBuilder(X86::MOV64ri).addReg(X86::RAX).addExpr(Callee));
+      EmitAndCountInstruction(MCInstBuilder(X86::ADD64rr)
+                                  .addReg(X86::RAX)
+                                  .addReg(X86::RAX)
+                                  .addReg(X86::R11));
+      EmitAndCountInstruction(MCInstBuilder(X86::CALL64r).addReg(X86::RAX));
+      return;
+    }
     if (NeedsPadding && Is64BitsLP64)
       EmitAndCountInstruction(MCInstBuilder(X86::DATA16_PREFIX));
     EmitAndCountInstruction(MCInstBuilder(X86::LEA64r)
@@ -604,7 +645,6 @@ void X86AsmPrinter::LowerTlsAddr(X86MCInstLower &MCInstLowering,
                                 .addReg(0)
                                 .addExpr(Sym)
                                 .addReg(0));
-    const MCSymbol *TlsGetAddr = Ctx.getOrCreateSymbol("__tls_get_addr");
     if (NeedsPadding) {
       if (!UseGot)
         EmitAndCountInstruction(MCInstBuilder(X86::DATA16_PREFIX));
diff --git a/llvm/test/CodeGen/X86/code-model-elf.ll b/llvm/test/CodeGen/X86/code-model-elf.ll
index aa2cc3a4981a2..574fd7188bba7 100644
--- a/llvm/test/CodeGen/X86/code-model-elf.ll
+++ b/llvm/test/CodeGen/X86/code-model-elf.ll
@@ -1324,29 +1324,6 @@ define dso_local ptr @lea_dso_local_ifunc() #0 {
   ret ptr @dso_local_ifunc_func
 }
 
-; FIXME: The result is same for small, medium and large model, because we
-; specify pie option in the test case. And the type of tls is initial exec tls.
-; For pic code. The large model code for pic tls should be emitted as below.
-
-; .L3:
-; leaq	.L3(%rip), %rbx
-; movabsq	$_GLOBAL_OFFSET_TABLE_-.L3, %r11
-; addq	%r11, %rbx
-; leaq	thread_data at TLSGD(%rip), %rdi
-; movabsq	$__tls_get_addr at PLTOFF, %rax
-; addq	%rbx, %rax
-; call	*%rax
-; movl	(%rax), %eax
-
-; The medium and small model code for pic tls should be emitted as below.
-; data16
-; leaq	thread_data at TLSGD(%rip), %rdi
-; data16
-; data16
-; rex64
-; callq	__tls_get_addr at PLT
-; movl	(%rax), %eax
-
 define dso_local i32 @load_thread_data() #0 {
 ; CHECK-LABEL: load_thread_data:
 ; CHECK:       # %bb.0:
diff --git a/llvm/test/CodeGen/X86/tls-large-model.ll b/llvm/test/CodeGen/X86/tls-large-model.ll
new file mode 100644
index 0000000000000..e32c395c6e6fa
--- /dev/null
+++ b/llvm/test/CodeGen/X86/tls-large-model.ll
@@ -0,0 +1,47 @@
+; RUN: llc < %s -mtriple=x86_64-linux-gnu -relocation-model=pic \
+; RUN:   -code-model=large | FileCheck %s
+; RUN: llc < %s -mtriple=x86_64-linux-gnu -relocation-model=pic \
+; RUN:   -code-model=large -filetype=obj -o - | llvm-readobj -r - | \
+; RUN:   FileCheck %s --check-prefix=RELOC
+
+; RELOC-DAG: R_X86_64_GOTPC64 _GLOBAL_OFFSET_TABLE_
+; RELOC-DAG: R_X86_64_GOTPC64 _GLOBAL_OFFSET_TABLE_
+; RELOC-DAG: R_X86_64_TLSGD gd
+; RELOC-DAG: R_X86_64_PLTOFF64 __tls_get_addr
+; RELOC-DAG: R_X86_64_PLTOFF64 __tls_get_addr
+; RELOC-DAG: R_X86_64_TLSLD ld
+
+ at gd = external thread_local global i32
+ at ld = external thread_local(localdynamic) global i32
+
+define i32 @load_gd() {
+; CHECK-LABEL: load_gd:
+; CHECK:       [[PB:\.L[^:]+]]:
+; CHECK-NEXT:    leaq [[PB]](%rip), %r11
+; CHECK-NEXT:    movabsq $_GLOBAL_OFFSET_TABLE_-[[PB]], %rax
+; CHECK-NEXT:    addq %rax, %r11
+; CHECK-NEXT:    leaq gd at TLSGD(%rip), %rdi
+; CHECK-NEXT:    movabsq $__tls_get_addr at PLTOFF, %rax
+; CHECK-NEXT:    addq %r11, %rax
+; CHECK-NEXT:    callq *%rax
+; CHECK-NEXT:    movl (%rax), %eax
+; CHECK:         retq
+  %v = load i32, ptr @gd
+  ret i32 %v
+}
+
+define i32 @load_ld() {
+; CHECK-LABEL: load_ld:
+; CHECK:       [[PB:\.L[^:]+]]:
+; CHECK-NEXT:    leaq [[PB]](%rip), %r11
+; CHECK-NEXT:    movabsq $_GLOBAL_OFFSET_TABLE_-[[PB]], %rax
+; CHECK-NEXT:    addq %rax, %r11
+; CHECK-NEXT:    leaq ld at TLSLD(%rip), %rdi
+; CHECK-NEXT:    movabsq $__tls_get_addr at PLTOFF, %rax
+; CHECK-NEXT:    addq %r11, %rax
+; CHECK-NEXT:    callq *%rax
+; CHECK-NEXT:    movl (%rbx,%rax), %eax
+; CHECK:         retq
+  %v = load i32, ptr @ld
+  ret i32 %v
+}



More information about the llvm-commits mailing list