[lld] [ELF,SPARC] Handle GOTDATA relocations (PR #213497)

Fangrui Song via llvm-commits llvm-commits at lists.llvm.org
Sat Aug 1 18:16:37 PDT 2026


https://github.com/MaskRay updated https://github.com/llvm/llvm-project/pull/213497

>From d5dfb10b96cff215eb7647d700dc7817f325eeb0 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Mon, 27 Jul 2026 00:06:31 -0700
Subject: [PATCH 1/2] [ELF,SPARC] Handle GOTDATA relocations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

A PIC data reference forms the symbol's GOT offset with
R_SPARC_GOTDATA_OP_HIX22 and R_SPARC_GOTDATA_OP_LOX10 and loads through
it, with R_SPARC_GOTDATA_OP marking the load:

```
sethi %gdop_hix22(sym), %g1
xor   %g1, %gdop_lox10(sym), %g1
ldx   [%l7 + %g1], %g1, %gdop(sym)
```

When the symbol is neither preemptible nor absolute, optimize the load
to `add %l7, %g1, %g1` over the symbol's GOT-relative address. An
absolute symbol keeps the GOT load, as it can be arbitrarily far from
.got.

Co-authored-by: Kirill A. Korinsky <kirill at korins.ky>
Co-authored-by: LemonBoy <thatlemon at gmail.com>
Co-authored-by: Alex Rønne Petersen <alex at alexrp.com>
---
 lld/ELF/Arch/SPARCV9.cpp       |  32 +++++++++
 lld/test/ELF/sparcv9-gotdata.s | 124 +++++++++++++++++++++++++++++++++
 2 files changed, 156 insertions(+)
 create mode 100644 lld/test/ELF/sparcv9-gotdata.s

diff --git a/lld/ELF/Arch/SPARCV9.cpp b/lld/ELF/Arch/SPARCV9.cpp
index a82ae78c958f5..c35208f027e4e 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -166,6 +166,20 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
       expr = R_GOT_OFF;
       break;
 
+    // Optimize the GOT load to an add of the symbol's GOT-relative address if
+    // applicable. Exclude absolute symbol, which can be arbitrarily far. %l7
+    // points at .got, which must be retained.
+    case R_SPARC_GOTDATA_OP_HIX22:
+    case R_SPARC_GOTDATA_OP_LOX10:
+    case R_SPARC_GOTDATA_OP:
+      if (sym.isPreemptible || isAbsolute(sym)) {
+        expr = R_GOT_OFF;
+      } else {
+        ctx.in.got->hasGotOffRel.store(true, std::memory_order_relaxed);
+        expr = R_GOTREL;
+      }
+      break;
+
     // TLS LE relocations:
     case R_SPARC_TLS_LE_HIX22:
     case R_SPARC_TLS_LE_LOX10:
@@ -322,6 +336,24 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
     // T-simm13
     write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) | 0x1C00);
     break;
+  case R_SPARC_GOTDATA_OP_HIX22: {
+    // V-imm22. sethi encodes the complement of a negative value, which the
+    // paired xor undoes, so the encodable range is 33 bits.
+    checkInt(ctx, loc, val, 33, rel);
+    uint64_t v = int64_t(val) < 0 ? ~val : val;
+    write32be(loc, (read32be(loc) & ~0x003fffff) | ((v >> 10) & 0x003fffff));
+    break;
+  }
+  case R_SPARC_GOTDATA_OP_LOX10:
+    // T-simm13. Only a negative value needs the sign extension bits.
+    write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) |
+                       (int64_t(val) < 0 ? 0x1c00 : 0));
+    break;
+  case R_SPARC_GOTDATA_OP:
+    // ldx [%rs1 + %rs2], %rd -> add %rs1, %rs2, %rd
+    if (rel.expr == R_GOTREL)
+      write32be(loc, (read32be(loc) & 0x3e07c01f) | 0x80000000);
+    break;
   default:
     llvm_unreachable("unknown relocation");
   }
diff --git a/lld/test/ELF/sparcv9-gotdata.s b/lld/test/ELF/sparcv9-gotdata.s
new file mode 100644
index 0000000000000..4287aa081eb1c
--- /dev/null
+++ b/lld/test/ELF/sparcv9-gotdata.s
@@ -0,0 +1,124 @@
+# REQUIRES: sparc
+## Test R_SPARC_GOTDATA_OP_HIX22, R_SPARC_GOTDATA_OP_LOX10 and R_SPARC_GOTDATA_OP.
+## The GOT load is optimized to an add of the symbol's GOT-relative address
+## unless the symbol is preemptible or absolute.
+
+# 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: llvm-mc -filetype=obj -triple=sparcv9 c.s -o c.o
+
+# RUN: ld.lld -shared a.o c.o -o a.so
+# RUN: llvm-readelf -S -r a.so | FileCheck %s --check-prefix=RELOC
+# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a.so | FileCheck %s
+
+# RELOC:      [ 8] .got PROGBITS 0000000000200390 {{[0-9a-f]+}} 000020
+# RELOC:      Relocation section '.rela.dyn' {{.*}} contains 1 entries:
+# RELOC:      0000000000200398 {{.*}} R_SPARC_GLOB_DAT {{.*}} foo + 0
+
+## foo is preemptible, so the GOT load stays.
+## .got[1] - _GLOBAL_OFFSET_TABLE_ = 0x200398 - 0x200390 = 8.
+# CHECK-LABEL: <_start>:
+# CHECK-NEXT:   sethi 0, %l1
+# CHECK-NEXT:   xor %l1, 8, %l1
+# CHECK-NEXT:   ldx [%l7+%l1], %l2
+## hid0 - _GLOBAL_OFFSET_TABLE_ = 0x3003b0 - 0x200390 = 0x100020.
+# CHECK-NEXT:   sethi 1024, %g1
+# CHECK-NEXT:   xor %g1, 32, %g1
+# CHECK-NEXT:   add %l7, %g1, %g1
+## hid1 - _GLOBAL_OFFSET_TABLE_ = 0x1002ec - 0x200390 = -0x1000a4. sethi holds
+## the complement, which the sign-extended xor operand undoes.
+# CHECK-NEXT:   sethi 1024, %o2
+# CHECK-NEXT:   xor %o2, -164, %o2
+# CHECK-NEXT:   add %i0, %o2, %i5
+## An undefined symbol and an absolute symbol both have an absolute address, so
+## neither is optimized.
+# CHECK-NEXT:   sethi 0, %l1
+# CHECK-NEXT:   xor %l1, 16, %l1
+# CHECK-NEXT:   ldx [%l7+%l1], %l2
+# CHECK-NEXT:   sethi 0, %g4
+# CHECK-NEXT:   xor %g4, 24, %g4
+# CHECK-NEXT:   ldx [%l7+%g4], %o3
+
+## foo is not preemptible in an executable and is optimized as well. undefweak
+## and absolute keep the GOT load in every mode.
+# RUN: ld.lld a.o c.o -o a
+# RUN: llvm-objdump -d -j .text --no-show-raw-insn --no-print-imm-hex a | FileCheck %s --check-prefix=NOPIE
+# NOPIE-LABEL: <_start>:
+# NOPIE-NEXT:   sethi 1024, %l1
+# NOPIE-NEXT:   xor %l1, -4, %l1
+# NOPIE-NEXT:   add %l7, %l1, %l2
+# NOPIE:        sethi 0, %l1
+# NOPIE-NEXT:   xor %l1, 8, %l1
+# NOPIE-NEXT:   ldx [%l7+%l1], %l2
+# NOPIE-NEXT:   sethi 0, %g4
+# NOPIE-NEXT:   xor %g4, 16, %g4
+# NOPIE-NEXT:   ldx [%l7+%g4], %o3
+
+## .got holds nothing but the header, yet must be kept: %l7 points at it.
+# RUN: ld.lld -shared b.o -o b.so
+# RUN: llvm-readelf -S b.so | FileCheck %s --check-prefix=HEADER
+# HEADER: [ 7] .got PROGBITS 00000000002002b8 {{[0-9a-f]+}} 000008
+
+## The optimization is decided before addresses are known, so an out-of-range
+## offset is an error rather than a fallback to the GOT load. The offset is
+## reported as signed, not as the complement the sethi encodes. Only hid0, in
+## .data, is out of range; the other symbols stay next to .got.
+# RUN: not ld.lld -shared a.o c.o --section-start=.text=0x10000 --section-start=.got=0x20000 \
+# RUN:   --section-start=.data=0x200000000 2>&1 | FileCheck %s --check-prefix=ERR -DVAL=8589803520 --implicit-check-not=error:
+# RUN: not ld.lld -shared a.o c.o --section-start=.data=0x10000 --section-start=.text=0x200000000 \
+# RUN:   --section-start=.got=0x200020000 2>&1 | FileCheck %s --check-prefix=ERR -DVAL=-8590000128 --implicit-check-not=error:
+# ERR: error: a.o:(.text+0xc): relocation R_SPARC_GOTDATA_OP_HIX22 out of range: [[VAL]] is not in [-4294967296, 4294967295]; references 'hid0'
+
+#--- a.s
+.text
+.globl _start
+_start:
+  sethi %gdop_hix22(foo), %l1
+  xor   %l1, %gdop_lox10(foo), %l1
+  ldx   [%l7 + %l1], %l2, %gdop(foo)
+
+  sethi %gdop_hix22(hid0), %g1
+  xor   %g1, %gdop_lox10(hid0), %g1
+  ldx   [%l7 + %g1], %g1, %gdop(hid0)
+
+## The add keeps rs1, rs2 and rd of the load, and ld is optimized like ldx.
+  sethi %gdop_hix22(hid1), %o2
+  xor   %o2, %gdop_lox10(hid1), %o2
+  ld    [%i0 + %o2], %i5, %gdop(hid1)
+
+  sethi %gdop_hix22(undefweak), %l1
+  xor   %l1, %gdop_lox10(undefweak), %l1
+  ldx   [%l7 + %l1], %l2, %gdop(undefweak)
+
+  sethi %gdop_hix22(absolute), %g4
+  xor   %g4, %gdop_lox10(absolute), %g4
+  ldx   [%l7 + %g4], %o3, %gdop(absolute)
+
+.globl foo, hid0, hid1
+.hidden hid0, hid1
+foo:
+hid1:
+  nop
+
+.weak undefweak
+.hidden undefweak
+
+.data
+hid0:
+  .xword 0
+
+#--- b.s
+.globl _start
+_start:
+  sethi %gdop_hix22(local), %l1
+  xor   %l1, %gdop_lox10(local), %l1
+  ldx   [%l7 + %l1], %l2, %gdop(local)
+local:
+  nop
+
+#--- c.s
+## Separate file to test %gdop_lox10(absolute) relocations in a.s
+.globl absolute
+.hidden absolute
+absolute = 0x1234

>From 90ad4edde0c6d51e6ae163eb91e87b0c4193a9f6 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i at maskray.me>
Date: Sat, 1 Aug 2026 18:16:12 -0700
Subject: [PATCH 2/2] ifunc test

---
 lld/test/ELF/sparcv9-gotdata.s | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/lld/test/ELF/sparcv9-gotdata.s b/lld/test/ELF/sparcv9-gotdata.s
index 4287aa081eb1c..2a95c7ee61413 100644
--- a/lld/test/ELF/sparcv9-gotdata.s
+++ b/lld/test/ELF/sparcv9-gotdata.s
@@ -7,6 +7,7 @@
 # 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: llvm-mc -filetype=obj -triple=sparcv9 c.s -o c.o
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 ifunc.s -o ifunc.o
 
 # RUN: ld.lld -shared a.o c.o -o a.so
 # RUN: llvm-readelf -S -r a.so | FileCheck %s --check-prefix=RELOC
@@ -60,6 +61,9 @@
 # RUN: llvm-readelf -S b.so | FileCheck %s --check-prefix=HEADER
 # HEADER: [ 7] .got PROGBITS 00000000002002b8 {{[0-9a-f]+}} 000008
 
+# RUN: not ld.lld -shared ifunc.o 2>&1 | FileCheck %s --check-prefix=IFUNC --implicit-check-not=error:
+# IFUNC: error: {{.*}}relocation R_SPARC_WDISP19 out of range
+
 ## The optimization is decided before addresses are known, so an out-of-range
 ## offset is an error rather than a fallback to the GOT load. The offset is
 ## reported as signed, not as the complement the sethi encodes. Only hid0, in
@@ -122,3 +126,16 @@ local:
 .globl absolute
 .hidden absolute
 absolute = 0x1234
+
+#--- ifunc.s
+.globl _start
+_start:
+  sethi %gdop_hix22(ifunc), %l1
+  xor   %l1, %gdop_lox10(ifunc), %l1
+  ldx   [%l7 + %l1], %l2, %gdop(ifunc)
+
+.globl ifunc
+.hidden ifunc
+.type ifunc, @gnu_indirect_function
+ifunc:
+  nop



More information about the llvm-commits mailing list