[lld] [ELF,SPARC] Handle TLS GD and LD relocations (PR #228197)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Oct 1 11:55:22 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-lld
Author: Imre Kaloz (kaloz)
<details>
<summary>Changes</summary>
A general-dynamic reference forms a GOT offset and calls __tls_get_addr:
sethi %tgd_hi22(sym), %o0
add %o0, %tgd_lo10(sym), %o0
add %l7, %o0, %o0, %tgd_add(sym)
call __tls_get_addr, %tgd_call(sym)
The GOT entry holds the module index and offset. Local dynamic uses
%tldm/%tldo_hix22/%tldo_lox10 the same way; @<!-- -->dtpoff is non-negative, so
HIX/LOX take the plain high/low split.
The call relocation names the TLS symbol, not the callee, so a call
surviving optimization is rebound to __tls_get_addr in
finalizeRelocScan(), after the parallel scan.
An executable optimizes general dynamic to initial exec or local exec,
local dynamic to local exec.
Signed-off-by: Imre Kaloz <kaloz@<!-- -->kernel.org>
---
Full diff: https://github.com/llvm/llvm-project/pull/228197.diff
3 Files Affected:
- (modified) lld/ELF/Arch/SPARCV9.cpp (+151)
- (added) lld/test/ELF/sparcv9-tls-gd.s (+87)
- (added) lld/test/ELF/sparcv9-tls-ld.s (+78)
``````````diff
diff --git a/lld/ELF/Arch/SPARCV9.cpp b/lld/ELF/Arch/SPARCV9.cpp
index 7797990571def..d777daea7bfd5 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -6,7 +6,9 @@
//
//===----------------------------------------------------------------------===//
+#include "InputFiles.h"
#include "RelocScan.h"
+#include "SymbolTable.h"
#include "Symbols.h"
#include "SyntheticSections.h"
#include "Target.h"
@@ -30,6 +32,7 @@ class SPARCV9 final : public TargetInfo {
void writeGotHeader(uint8_t *buf) const override;
void writePlt(uint8_t *buf, const Symbol &sym,
uint64_t pltEntryAddr) const override;
+ void finalizeRelocScan() override;
template <class ELFT, class RelTy>
void scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
unsigned shard);
@@ -48,6 +51,8 @@ SPARCV9::SPARCV9(Ctx &ctx) : TargetInfo(ctx) {
relativeRel = R_SPARC_RELATIVE;
symbolicRel = R_SPARC_64;
tlsGotRel = R_SPARC_TLS_TPOFF64;
+ tlsModuleIndexRel = R_SPARC_TLS_DTPMOD64;
+ tlsOffsetRel = R_SPARC_TLS_DTPOFF64;
gotHeaderEntriesNum = 1;
pltEntrySize = 32;
pltHeaderSize = 4 * pltEntrySize;
@@ -118,6 +123,7 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
switch (type) {
case R_SPARC_NONE:
case R_SPARC_TLS_IE_ADD:
+ case R_SPARC_TLS_LDO_ADD:
continue;
// Absolute relocations:
@@ -182,6 +188,44 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
}
break;
+ // TLS GD relocations. In an executable the sequence is optimized to
+ // Initial Exec for a preemptible symbol and to Local Exec otherwise.
+ case R_SPARC_TLS_GD_HI22:
+ case R_SPARC_TLS_GD_LO10:
+ rs.handleTlsGd(R_TLSGD_GOT, R_GOT_OFF, R_TPREL, type, offset, addend,
+ sym);
+ continue;
+ case R_SPARC_TLS_GD_ADD:
+ // A marker on the add. R_ABS is a dummy for the unoptimized sequence and
+ // writes nothing; an optimized one rewrites the instruction.
+ rs.handleTlsGd(R_ABS, R_GOT_OFF, R_TPREL, type, offset, addend, sym);
+ continue;
+ case R_SPARC_TLS_GD_CALL:
+ // The call names the TLS symbol rather than __tls_get_addr, so an
+ // unoptimized call is rebound by finalizeRelocScan().
+ rs.handleTlsGd(R_PLT_PC, R_GOT_OFF, R_TPREL, type, offset, addend, sym);
+ continue;
+
+ // TLS LD relocations. In an executable the sequence is optimized to
+ // Local Exec.
+ case R_SPARC_TLS_LDM_HI22:
+ case R_SPARC_TLS_LDM_LO10:
+ rs.handleTlsLd(R_TLSLD_GOT, type, offset, addend, sym);
+ continue;
+ case R_SPARC_TLS_LDM_ADD:
+ rs.handleTlsLd(R_ABS, type, offset, addend, sym);
+ continue;
+ case R_SPARC_TLS_LDM_CALL:
+ rs.handleTlsLd(R_PLT_PC, type, offset, addend, sym);
+ continue;
+ case R_SPARC_TLS_LDO_HIX22:
+ case R_SPARC_TLS_LDO_LOX10:
+ // @dtpoff is a non-negative offset into the module's TLS block, so it
+ // uses the plain high/low split despite the HIX/LOX names. Local Exec
+ // makes it the negative @tpoff, which needs the complement encoding.
+ expr = ctx.arg.shared ? R_DTPREL : R_TPREL;
+ break;
+
// TLS LE relocations:
case R_SPARC_TLS_LE_HIX22:
case R_SPARC_TLS_LE_LOX10:
@@ -394,11 +438,118 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
: 0x80100000 | (insn & 0x3e00001f));
break;
}
+ case R_SPARC_TLS_DTPMOD64:
+ case R_SPARC_TLS_DTPOFF64:
+ case R_SPARC_TLS_TPOFF64:
+ // V-xword64. A GOT slot the link resolves: the module index of the output
+ // module, or an offset within a module whose TLS block is known.
+ write64be(loc, val);
+ break;
+ case R_SPARC_TLS_GD_HI22: {
+ // T-imm22. Local Exec encodes the complement, as R_SPARC_TLS_LE_HIX22 does.
+ uint64_t v = rel.expr == R_TPREL ? ~val : val;
+ write32be(loc, (read32be(loc) & ~0x003fffff) | ((v >> 10) & 0x003fffff));
+ break;
+ }
+ case R_SPARC_TLS_GD_LO10:
+ if (rel.expr == R_TPREL)
+ // add %rs1, imm, %rd -> xor %rs1, imm, %rd, T-simm13.
+ write32be(loc, (read32be(loc) & ~0x00001fff) | 0x80182000 |
+ (val & 0x000003ff) | 0x1c00);
+ else
+ // T-simm10
+ write32be(loc, (read32be(loc) & ~0x000003ff) | (val & 0x000003ff));
+ break;
+ case R_SPARC_TLS_GD_ADD:
+ if (rel.expr == R_GOT_OFF)
+ // Initial Exec: add %rs1, %rs2, %rd -> ldx [%rs1 + %rs2], %rd.
+ write32be(loc, (read32be(loc) & 0x3e07c01f) | 0xc0000000 | (0x0b << 19));
+ else if (rel.expr == R_TPREL)
+ // Local Exec: the GOT pointer becomes the thread pointer, %rs1 -> %g7.
+ write32be(loc, (read32be(loc) & ~0x0007c000) | (7 << 14));
+ break;
+ case R_SPARC_TLS_GD_CALL:
+ if (rel.expr == R_GOT_OFF)
+ write32be(loc, 0x9001c008); // add %g7, %o0, %o0
+ else if (rel.expr == R_TPREL)
+ write32be(loc, 0x01000000); // nop
+ else
+ // V-disp30, the call to __tls_get_addr.
+ write32be(loc, (read32be(loc) & ~0x3fffffff) | ((val >> 2) & 0x3fffffff));
+ break;
+ case R_SPARC_TLS_LDM_HI22:
+ if (rel.expr == R_TPREL)
+ write32be(loc, 0x01000000); // nop
+ else
+ // T-imm22
+ write32be(loc,
+ (read32be(loc) & ~0x003fffff) | ((val >> 10) & 0x003fffff));
+ break;
+ case R_SPARC_TLS_LDM_LO10:
+ if (rel.expr == R_TPREL)
+ write32be(loc, 0x01000000); // nop
+ else
+ // T-simm10
+ write32be(loc, (read32be(loc) & ~0x000003ff) | (val & 0x000003ff));
+ break;
+ case R_SPARC_TLS_LDM_ADD:
+ if (rel.expr == R_TPREL)
+ write32be(loc, 0x01000000); // nop
+ break;
+ case R_SPARC_TLS_LDM_CALL:
+ if (rel.expr == R_TPREL)
+ // Local Exec: the paired LDO add takes the thread pointer from %o0.
+ write32be(loc, 0x90100007); // mov %g7, %o0
+ else
+ // V-disp30, the call to __tls_get_addr.
+ write32be(loc, (read32be(loc) & ~0x3fffffff) | ((val >> 2) & 0x3fffffff));
+ break;
+ case R_SPARC_TLS_LDO_HIX22: {
+ // T-imm22
+ uint64_t v = rel.expr == R_TPREL ? ~val : val;
+ write32be(loc, (read32be(loc) & ~0x003fffff) | ((v >> 10) & 0x003fffff));
+ break;
+ }
+ case R_SPARC_TLS_LDO_LOX10:
+ // T-simm13. Only the negative @tpoff needs the sign extension bits.
+ write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) |
+ (rel.expr == R_TPREL ? 0x1c00 : 0));
+ break;
default:
llvm_unreachable("unknown relocation");
}
}
+void SPARCV9::finalizeRelocScan() {
+ Symbol *tga = nullptr;
+
+ // R_SPARC_TLS_GD_CALL/LDM_CALL name the TLS symbol, not the callee. Rebind
+ // the calls that survived optimization (recorded as R_PLT_PC by
+ // scanSectionImpl) to __tls_get_addr. The symbol table cannot be reached
+ // from scanSectionImpl, which runs in parallel.
+ for (ELFFileBase *f : ctx.objectFiles) {
+ for (InputSectionBase *s : f->getSections()) {
+ auto *isec = dyn_cast_or_null<InputSection>(s);
+ if (!isec || !isec->isLive())
+ continue;
+ for (Relocation &rel : isec->relocs()) {
+ if (rel.expr != R_PLT_PC || (rel.type != R_SPARC_TLS_GD_CALL &&
+ rel.type != R_SPARC_TLS_LDM_CALL))
+ continue;
+ if (!tga) {
+ tga = ctx.symtab->addSymbol(Undefined{ctx.internalFile,
+ "__tls_get_addr", STB_GLOBAL,
+ STV_DEFAULT, STT_FUNC});
+ tga->isUsedInRegularObj = true;
+ tga->isPreemptible = true;
+ tga->setFlags(NEEDS_PLT | USED);
+ }
+ rel.sym = tga;
+ }
+ }
+ }
+}
+
void SPARCV9::writeGotHeader(uint8_t *buf) const {
// _GLOBAL_OFFSET_TABLE_[0] = _DYNAMIC
write64be(buf, ctx.in.dynamic->getVA());
diff --git a/lld/test/ELF/sparcv9-tls-gd.s b/lld/test/ELF/sparcv9-tls-gd.s
new file mode 100644
index 0000000000000..ddde0a65c79b7
--- /dev/null
+++ b/lld/test/ELF/sparcv9-tls-gd.s
@@ -0,0 +1,87 @@
+# REQUIRES: sparc
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 a.s -o a.o
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 b.s -o b.o
+# RUN: ld.lld -shared b.o -o b.so
+# RUN: ld.lld -shared a.o b.so -o a.so
+# RUN: llvm-readelf -r a.so | FileCheck %s --check-prefix=GD-REL
+# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a.so | FileCheck %s --check-prefix=GD
+
+## Each sequence gets a two-slot GOT entry holding the module index and the
+## offset within that module's TLS block. a0 is hidden, so its offset is known
+## at link time and only the module index needs a dynamic relocation. The call
+## goes through the PLT even though the relocation names the TLS symbol rather
+## than __tls_get_addr.
+# GD-REL: Relocation section '.rela.dyn' {{.*}} contains 3 entries:
+# GD-REL: R_SPARC_TLS_DTPMOD64 0{{$}}
+# GD-REL-DAG: R_SPARC_TLS_DTPMOD64 {{.*}} b + 0
+# GD-REL-DAG: R_SPARC_TLS_DTPOFF64 {{.*}} b + 0
+# GD-REL: Relocation section '.rela.plt' {{.*}} contains 1 entries:
+# GD-REL: R_SPARC_JMP_SLOT {{.*}} __tls_get_addr + 0
+
+# GD-LABEL: <_start>:
+# GD-NEXT: sethi 0, %o0
+# GD-NEXT: add %o0, 8, %o0
+# GD-NEXT: add %l7, %o0, %o0
+# GD-NEXT: call
+# GD-NEXT: nop
+# GD-NEXT: sethi 0, %o1
+# GD-NEXT: add %o1, 24, %o1
+# GD-NEXT: add %l7, %o1, %o0
+# GD-NEXT: call
+# GD-NEXT: nop
+
+## In an executable a0 is not preemptible, so its sequence becomes Local Exec:
+## the sethi holds the complement of the offset, the add becomes an xor, the
+## GOT pointer becomes the thread pointer and the call is dropped. b is
+## preemptible, so its sequence becomes Initial Exec instead: the add becomes
+## the GOT load and the call becomes the thread-pointer add.
+# RUN: ld.lld a.o b.so -o a
+# RUN: llvm-readelf -r a | FileCheck %s --check-prefix=EXE-REL
+# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a | FileCheck %s --check-prefix=EXE
+
+# EXE-REL: Relocation section '.rela.dyn' {{.*}} contains 1 entries:
+# EXE-REL: R_SPARC_TLS_TPOFF64 {{.*}} b + 0
+# EXE-REL-NOT: R_SPARC_TLS_DTPMOD64
+# EXE-REL-NOT: __tls_get_addr
+
+# EXE-LABEL: <_start>:
+# EXE-NEXT: sethi 0, %o0
+# EXE-NEXT: xor %o0, -8, %o0
+# EXE-NEXT: add %g7, %o0, %o0
+# EXE-NEXT: nop
+# EXE-NEXT: nop
+# EXE-NEXT: sethi 0, %o1
+# EXE-NEXT: add %o1, 8, %o1
+# EXE-NEXT: ldx [%l7+%o1], %o0
+# EXE-NEXT: add %g7, %o0, %o0
+# EXE-NEXT: nop
+
+#--- a.s
+.globl _start
+_start:
+## a0 is hidden, so it is not preemptible in an executable.
+ sethi %tgd_hi22(a0), %o0
+ add %o0, %tgd_lo10(a0), %o0
+ add %l7, %o0, %o0, %tgd_add(a0)
+ call __tls_get_addr, %tgd_call(a0)
+ nop
+
+## b is defined in a DSO, so it stays preemptible in an executable.
+ sethi %tgd_hi22(b), %o1
+ add %o1, %tgd_lo10(b), %o1
+ add %l7, %o1, %o0, %tgd_add(b)
+ call __tls_get_addr, %tgd_call(b)
+ nop
+
+.section .tbss,"awT", at nobits
+.globl a0
+.hidden a0
+a0:
+ .xword 0
+
+#--- b.s
+.section .tbss,"awT", at nobits
+.globl b
+b:
+ .xword 0
diff --git a/lld/test/ELF/sparcv9-tls-ld.s b/lld/test/ELF/sparcv9-tls-ld.s
new file mode 100644
index 0000000000000..843cb2d856cfa
--- /dev/null
+++ b/lld/test/ELF/sparcv9-tls-ld.s
@@ -0,0 +1,78 @@
+# REQUIRES: sparc
+# RUN: rm -rf %t && split-file %s %t && cd %t
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 a.s -o a.o
+# RUN: ld.lld -shared a.o -o a.so
+# RUN: llvm-readelf -r a.so | FileCheck %s --check-prefix=LD-REL
+# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a.so | FileCheck %s --check-prefix=LD
+
+## Local Dynamic needs only the module index, so one GOT entry and one dynamic
+## relocation serve every symbol in the module. The per-symbol offsets are
+## link-time constants.
+# LD-REL: Relocation section '.rela.dyn' {{.*}} contains 1 entries:
+# LD-REL: R_SPARC_TLS_DTPMOD64 0{{$}}
+# LD-REL: Relocation section '.rela.plt' {{.*}} contains 1 entries:
+# LD-REL: R_SPARC_JMP_SLOT {{.*}} __tls_get_addr + 0
+
+# LD-LABEL: <_start>:
+# LD-NEXT: sethi 0, %o0
+# LD-NEXT: add %o0, 8, %o0
+# LD-NEXT: add %l7, %o0, %o0
+# LD-NEXT: call
+# LD-NEXT: nop
+## a0 is at offset 0 in the TLS block, a1 at 8.
+# LD-NEXT: sethi 0, %o1
+# LD-NEXT: xor %o1, 0, %o1
+# LD-NEXT: add %o0, %o1, %o2
+# LD-NEXT: sethi 0, %o3
+# LD-NEXT: xor %o3, 8, %o3
+# LD-NEXT: add %o0, %o3, %o4
+
+## In an executable the whole sequence becomes Local Exec: the module-index
+## setup is dropped, the call loads the thread pointer into %o0, and the
+## per-symbol offsets become the negative @tpoff, encoded as a complement.
+# RUN: ld.lld a.o -o a
+# RUN: llvm-readelf -r a | FileCheck %s --check-prefix=LE-REL
+# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a | FileCheck %s --check-prefix=LE
+
+# LE-REL-NOT: R_SPARC_TLS
+# LE-REL-NOT: __tls_get_addr
+
+# LE-LABEL: <_start>:
+# LE-NEXT: nop
+# LE-NEXT: nop
+# LE-NEXT: nop
+# LE-NEXT: mov %g7, %o0
+# LE-NEXT: nop
+## a0 - tp = -0x10, a1 - tp = -8.
+# LE-NEXT: sethi 0, %o1
+# LE-NEXT: xor %o1, -16, %o1
+# LE-NEXT: add %o0, %o1, %o2
+# LE-NEXT: sethi 0, %o3
+# LE-NEXT: xor %o3, -8, %o3
+# LE-NEXT: add %o0, %o3, %o4
+
+#--- a.s
+.globl _start
+_start:
+ sethi %tldm_hi22(a0), %o0
+ add %o0, %tldm_lo10(a0), %o0
+ add %l7, %o0, %o0, %tldm_add(a0)
+ call __tls_get_addr, %tldm_call(a0)
+ nop
+
+ sethi %tldo_hix22(a0), %o1
+ xor %o1, %tldo_lox10(a0), %o1
+ add %o0, %o1, %o2, %tldo_add(a0)
+
+ sethi %tldo_hix22(a1), %o3
+ xor %o3, %tldo_lox10(a1), %o3
+ add %o0, %o3, %o4, %tldo_add(a1)
+
+.section .tbss,"awT", at nobits
+.globl a0, a1
+.hidden a0
+.hidden a1
+a0:
+ .xword 0
+a1:
+ .xword 0
``````````
</details>
https://github.com/llvm/llvm-project/pull/228197
More information about the llvm-commits
mailing list