[lld] Enough LLD changes to bootstrap OpenBSD on sparc64 (PR #207609)

Kirill A. Korinsky via llvm-commits llvm-commits at lists.llvm.org
Thu Jul 9 07:49:39 PDT 2026


https://github.com/catap updated https://github.com/llvm/llvm-project/pull/207609

>From e579ccacad0201efd209011e43765e4820b29c25 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 9 Jul 2026 16:46:38 +0200
Subject: [PATCH 1/7] [lld][ELF][SPARC] Use RELA relocations for SPARCV9
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: LemonBoy <thatlemon at gmail.com>
Co-authored-by: Alex Rønne Petersen <alex at alexrp.com>
---
 lld/ELF/Driver.cpp          |  7 ++++---
 lld/test/ELF/sparcv9-rela.s | 14 ++++++++++++++
 2 files changed, 18 insertions(+), 3 deletions(-)
 create mode 100644 lld/test/ELF/sparcv9-rela.s

diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 7ec7dfcae6bca..a2552a1ae1ab7 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -1318,9 +1318,10 @@ static SmallVector<StringRef, 0> getSymbolOrderingFile(Ctx &ctx,
 
 static bool getIsRela(Ctx &ctx, opt::InputArgList &args) {
   // The psABI specifies the default relocation entry format.
-  bool rela = is_contained({EM_AARCH64, EM_AMDGPU, EM_HEXAGON, EM_LOONGARCH,
-                            EM_PPC, EM_PPC64, EM_RISCV, EM_S390, EM_X86_64},
-                           ctx.arg.emachine);
+  bool rela =
+      is_contained({EM_AARCH64, EM_AMDGPU, EM_HEXAGON, EM_LOONGARCH, EM_PPC,
+                    EM_PPC64, EM_RISCV, EM_S390, EM_SPARCV9, EM_X86_64},
+                   ctx.arg.emachine);
   // If -z rel or -z rela is specified, use the last option.
   for (auto *arg : args.filtered(OPT_z)) {
     StringRef s(arg->getValue());
diff --git a/lld/test/ELF/sparcv9-rela.s b/lld/test/ELF/sparcv9-rela.s
new file mode 100644
index 0000000000000..2cebe2acffbed
--- /dev/null
+++ b/lld/test/ELF/sparcv9-rela.s
@@ -0,0 +1,14 @@
+# REQUIRES: sparc
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 %s -o %t.o
+# RUN: ld.lld -shared %t.o -o %t.so
+# RUN: llvm-readelf -r %t.so | FileCheck %s
+
+# CHECK: Relocation section '.rela.dyn'
+# CHECK: R_SPARC_RELATIVE
+
+.data
+.quad local
+
+.hidden local
+local:
+  .quad 0

>From 7d9f61081fc148d01c607ce465aa658031ec88d3 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 9 Jul 2026 16:46:48 +0200
Subject: [PATCH 2/7] [lld][ELF][SPARC] Handle additional SPARCV9 relocations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Add support for additional SPARCV9 absolute, PC-relative, and GOT relocation forms used by OpenBSD/sparc64.

Co-authored-by: LemonBoy <thatlemon at gmail.com>
Co-authored-by: Alex Rønne Petersen <alex at alexrp.com>
---
 lld/ELF/Arch/SPARCV9.cpp                | 81 ++++++++++++++++++++++---
 lld/test/ELF/sparcv9-reloc-additional.s | 67 ++++++++++++++++++++
 2 files changed, 138 insertions(+), 10 deletions(-)
 create mode 100644 lld/test/ELF/sparcv9-reloc-additional.s

diff --git a/lld/ELF/Arch/SPARCV9.cpp b/lld/ELF/Arch/SPARCV9.cpp
index 405be69a8f572..4fe3f24883bd8 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -92,10 +92,13 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
       continue;
 
     // Absolute relocations:
+    case R_SPARC_8:
+    case R_SPARC_16:
     case R_SPARC_32:
     case R_SPARC_UA32:
     case R_SPARC_64:
     case R_SPARC_UA64:
+    case R_SPARC_UA16:
     case R_SPARC_H44:
     case R_SPARC_M44:
     case R_SPARC_L44:
@@ -103,7 +106,10 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
     case R_SPARC_HM10:
     case R_SPARC_LM22:
     case R_SPARC_HI22:
+    case R_SPARC_13:
     case R_SPARC_LO10:
+    case R_SPARC_HIX22:
+    case R_SPARC_LOX10:
       expr = R_ABS;
       break;
 
@@ -115,13 +121,20 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
     // PC-relative relocations:
     case R_SPARC_PC10:
     case R_SPARC_PC22:
+    case R_SPARC_DISP8:
+    case R_SPARC_DISP16:
     case R_SPARC_DISP32:
     case R_SPARC_WDISP30:
+    case R_SPARC_WDISP22:
+    case R_SPARC_WDISP16:
+    case R_SPARC_WDISP19:
+    case R_SPARC_DISP64:
       rs.processR_PC(type, offset, addend, sym);
       continue;
 
     // GOT relocations:
     case R_SPARC_GOT10:
+    case R_SPARC_GOT13:
     case R_SPARC_GOT22:
       expr = R_GOT_OFF;
       break;
@@ -147,6 +160,17 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
 void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
                        uint64_t val) const {
   switch (rel.type) {
+  case R_SPARC_8:
+    // V-byte8
+    checkUInt(ctx, loc, val, 8, rel);
+    *loc = val;
+    break;
+  case R_SPARC_16:
+  case R_SPARC_UA16:
+    // V-half16
+    checkUInt(ctx, loc, val, 16, rel);
+    write16be(loc, val);
+    break;
   case R_SPARC_32:
   case R_SPARC_UA32:
     // V-word32
@@ -155,36 +179,67 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
     break;
   case R_SPARC_DISP32:
     // V-disp32
-    checkInt(ctx, loc, val, 32, rel);
+    checkIntUInt(ctx, loc, val, 32, rel);
     write32be(loc, val);
     break;
   case R_SPARC_WDISP30:
   case R_SPARC_WPLT30:
     // V-disp30
-    checkInt(ctx, loc, val, 32, rel);
+    checkIntUInt(ctx, loc, val, 32, rel);
     write32be(loc, (read32be(loc) & ~0x3fffffff) | ((val >> 2) & 0x3fffffff));
     break;
+  case R_SPARC_DISP8:
+    // V-byte8
+    checkIntUInt(ctx, loc, val, 8, rel);
+    *loc = val;
+    break;
+  case R_SPARC_DISP16:
+    // V-half16
+    checkIntUInt(ctx, loc, val, 16, rel);
+    write16be(loc, val);
+    break;
+  case R_SPARC_WDISP22:
+    // V-disp22
+    checkIntUInt(ctx, loc, val, 24, rel);
+    write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 2) & 0x003fffff));
+    break;
   case R_SPARC_22:
     // V-imm22
     checkUInt(ctx, loc, val, 22, rel);
     write32be(loc, (read32be(loc) & ~0x003fffff) | (val & 0x003fffff));
     break;
+  case R_SPARC_13:
+  case R_SPARC_GOT13:
+    // V-simm13
+    checkIntUInt(ctx, loc, val, 13, rel);
+    write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x00001fff));
+    break;
   case R_SPARC_GOT22:
-  case R_SPARC_PC22:
   case R_SPARC_LM22:
     // T-imm22
     write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 10) & 0x003fffff));
     break;
+  case R_SPARC_PC22:
+    // V-disp22
+    checkIntUInt(ctx, loc, val, 32, rel);
+    write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 10) & 0x003fffff));
+    break;
   case R_SPARC_HI22:
     // V-imm22
-    checkUInt(ctx, loc, val >> 10, 22, rel);
+    checkUInt(ctx, loc, val, 32, rel);
     write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 10) & 0x003fffff));
     break;
   case R_SPARC_WDISP19:
     // V-disp19
-    checkInt(ctx, loc, val, 21, rel);
+    checkIntUInt(ctx, loc, val, 21, rel);
     write32be(loc, (read32be(loc) & ~0x0007ffff) | ((val >> 2) & 0x0007ffff));
     break;
+  case R_SPARC_WDISP16:
+    // V-d2/disp14
+    checkIntUInt(ctx, loc, val, 18, rel);
+    write32be(loc, (read32be(loc) & ~0x0303fff) | (((val >> 2) & 0xc000) << 6) |
+                       ((val >> 2) & 0x00003fff));
+    break;
   case R_SPARC_GOT10:
   case R_SPARC_PC10:
     // T-simm10
@@ -192,25 +247,30 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
     break;
   case R_SPARC_LO10:
     // T-simm13
-    write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff));
+    write32be(loc, (read32be(loc) & ~0x000003ff) | (val & 0x000003ff));
     break;
   case R_SPARC_64:
+  case R_SPARC_DISP64:
   case R_SPARC_UA64:
     // V-xword64
     write64be(loc, val);
     break;
   case R_SPARC_HH22:
     // V-imm22
-    checkUInt(ctx, loc, val >> 42, 22, rel);
     write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 42) & 0x003fffff));
     break;
   case R_SPARC_HM10:
     // T-simm13
-    write32be(loc, (read32be(loc) & ~0x00001fff) | ((val >> 32) & 0x000003ff));
+    write32be(loc, (read32be(loc) & ~0x000003ff) | ((val >> 32) & 0x000003ff));
+    break;
+  case R_SPARC_HIX22:
+    // V-imm22
+    checkUInt(ctx, loc, ~val, 32, rel);
+    write32be(loc, (read32be(loc) & ~0x003fffff) | ((~val >> 10) & 0x003fffff));
     break;
   case R_SPARC_H44:
     // V-imm22
-    checkUInt(ctx, loc, val >> 22, 22, rel);
+    checkUInt(ctx, loc, val, 44, rel);
     write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 22) & 0x003fffff));
     break;
   case R_SPARC_M44:
@@ -219,12 +279,13 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
     break;
   case R_SPARC_L44:
     // T-imm13
-    write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x00000fff));
+    write32be(loc, (read32be(loc) & ~0x00000fff) | (val & 0x00000fff));
     break;
   case R_SPARC_TLS_LE_HIX22:
     // T-imm22
     write32be(loc, (read32be(loc) & ~0x003fffff) | ((~val >> 10) & 0x003fffff));
     break;
+  case R_SPARC_LOX10:
   case R_SPARC_TLS_LE_LOX10:
     // T-simm13
     write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) | 0x1C00);
diff --git a/lld/test/ELF/sparcv9-reloc-additional.s b/lld/test/ELF/sparcv9-reloc-additional.s
new file mode 100644
index 0000000000000..22fdae19068f2
--- /dev/null
+++ b/lld/test/ELF/sparcv9-reloc-additional.s
@@ -0,0 +1,67 @@
+# REQUIRES: sparc
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 %s -o %t.o
+# RUN: ld.lld %t.o --defsym=abs8=0x7f --defsym=abs16=0x1234 \
+# RUN:   --defsym=sym=0x12345678 --defsym=sym64=0x123456789abcdef \
+# RUN:   --defsym=sym44=0x123456789ab --defsym=small=0x123 \
+# RUN:   --defsym=neg1=0xffffffffffffffff -o %t
+# RUN: llvm-objdump -s --section=.data %t | FileCheck --check-prefix=HEX %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t | FileCheck --check-prefix=DIS %s
+# RUN: llvm-mc --position-independent -filetype=obj -triple=sparcv9 \
+# RUN:   --defsym=GOT=1 %s -o %t.got.o
+# RUN: ld.lld -shared %t.got.o -o %t.got.so
+# RUN: llvm-readelf -r %t.got.so | FileCheck --check-prefix=GOT %s
+
+.ifdef GOT
+# GOT: R_SPARC_GLOB_DAT {{.*}} value
+
+.text
+.globl _start
+_start:
+  ldx [%i0 + value], %i0
+.else
+## R_SPARC_8, R_SPARC_16, and R_SPARC_UA16.
+# HEX:      Contents of section .data:
+# HEX-NEXT: {{[0-9a-f]+}} 7f123412 34
+.data
+  .byte abs8
+  .half abs16
+  .uahalf abs16
+
+## R_SPARC_13, R_SPARC_HIX22, R_SPARC_LOX10, R_SPARC_HI22,
+## R_SPARC_LO10, R_SPARC_HH22, R_SPARC_HM10, R_SPARC_LM22,
+## R_SPARC_H44, R_SPARC_M44, R_SPARC_L44, R_SPARC_WDISP22,
+## R_SPARC_WDISP16, and R_SPARC_DISP64.
+# DIS-LABEL: <_start>:
+# DIS-NEXT:  mov 0x123, %g1
+# DIS-NEXT:  sethi 0x0, %g2
+# DIS-NEXT:  xor %g2, -0x1, %g2
+# DIS-NEXT:  sethi 0x48d15, %g3
+# DIS-NEXT:  or %g3, 0x278, %g3
+# DIS-NEXT:  sethi 0x48d1, %g4
+# DIS-NEXT:  or %g4, 0x167, %g4
+# DIS-NEXT:  sethi 0x226af3, %g5
+# DIS-NEXT:  sethi 0x48d15, %o0
+# DIS-NEXT:  or %o0, 0x278, %o0
+# DIS-NEXT:  or %o0, 0x9ab, %o0
+# DIS-NEXT:  ba,a
+# DIS-NEXT:  brnz
+.text
+.globl _start
+_start:
+  or %g0, small, %g1
+  sethi %hix(neg1), %g2
+  xor %g2, %lox(neg1), %g2
+  sethi %hi(sym), %g3
+  or %g3, %lo(sym), %g3
+  sethi %hh(sym64), %g4
+  or %g4, %hm(sym64), %g4
+  sethi %lm(sym64), %g5
+  sethi %h44(sym44), %o0
+  or %o0, %m44(sym44), %o0
+  or %o0, %l44(sym44), %o0
+  ba,a target
+  brnz %g1, target
+  .xword target - .
+target:
+  nop
+.endif

>From 3a76c4b43d87698fa264215f9b5bdd19b46a0977 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 9 Jul 2026 16:46:57 +0200
Subject: [PATCH 3/7] [lld][ELF][SPARC] Add SPARC GOT header entry
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: LemonBoy <thatlemon at gmail.com>
Co-authored-by: Alex Rønne Petersen <alex at alexrp.com>
---
 lld/ELF/Arch/SPARCV9.cpp          |  6 ++++++
 lld/test/ELF/sparcv9-got-header.s | 11 +++++++++++
 2 files changed, 17 insertions(+)
 create mode 100644 lld/test/ELF/sparcv9-got-header.s

diff --git a/lld/ELF/Arch/SPARCV9.cpp b/lld/ELF/Arch/SPARCV9.cpp
index 4fe3f24883bd8..130d01e4d34fb 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -25,6 +25,7 @@ class SPARCV9 final : public TargetInfo {
   SPARCV9(Ctx &);
   RelExpr getRelExpr(RelType type, const Symbol &s,
                      const uint8_t *loc) const override;
+  void writeGotHeader(uint8_t *buf) const override;
   void writePlt(uint8_t *buf, const Symbol &sym,
                 uint64_t pltEntryAddr) const override;
   template <class ELFT, class RelTy>
@@ -43,6 +44,7 @@ SPARCV9::SPARCV9(Ctx &ctx) : TargetInfo(ctx) {
   pltRel = R_SPARC_JMP_SLOT;
   relativeRel = R_SPARC_RELATIVE;
   symbolicRel = R_SPARC_64;
+  gotHeaderEntriesNum = 1;
   pltEntrySize = 32;
   pltHeaderSize = 4 * pltEntrySize;
 
@@ -295,6 +297,10 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
   }
 }
 
+void SPARCV9::writeGotHeader(uint8_t *buf) const {
+  write64be(buf, ctx.in.dynamic->getVA());
+}
+
 void SPARCV9::writePlt(uint8_t *buf, const Symbol & /*sym*/,
                        uint64_t pltEntryAddr) const {
   const uint8_t pltData[] = {
diff --git a/lld/test/ELF/sparcv9-got-header.s b/lld/test/ELF/sparcv9-got-header.s
new file mode 100644
index 0000000000000..d4630104e5fba
--- /dev/null
+++ b/lld/test/ELF/sparcv9-got-header.s
@@ -0,0 +1,11 @@
+# REQUIRES: sparc
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 %s -o %t.o
+# RUN: ld.lld -shared %t.o -o %t.so
+# RUN: llvm-readelf -S -x .got %t.so | FileCheck %s
+
+# CHECK:      .got PROGBITS {{[0-9a-f]+}} {{[0-9a-f]+}} 000008
+# CHECK-LABEL: Hex dump of section '.got':
+# CHECK-NEXT:  0x{{[0-9a-f]+}} {{[0-9a-f ]*[1-9a-f][0-9a-f ]*}}
+
+.data
+.xword _GLOBAL_OFFSET_TABLE_

>From b15234af03c32866e482c7ef56b8aee7421c5ab6 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 9 Jul 2026 16:47:16 +0200
Subject: [PATCH 4/7] [lld][ELF][SPARC] Support TLS IE relocations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: LemonBoy <thatlemon at gmail.com>
Co-authored-by: Alex Rønne Petersen <alex at alexrp.com>
---
 lld/ELF/Arch/SPARCV9.cpp      | 15 +++++++++++++++
 lld/test/ELF/sparcv9-tls-ie.s | 26 ++++++++++++++++++++++++++
 2 files changed, 41 insertions(+)
 create mode 100644 lld/test/ELF/sparcv9-tls-ie.s

diff --git a/lld/ELF/Arch/SPARCV9.cpp b/lld/ELF/Arch/SPARCV9.cpp
index 130d01e4d34fb..641bc1a670426 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -44,6 +44,7 @@ SPARCV9::SPARCV9(Ctx &ctx) : TargetInfo(ctx) {
   pltRel = R_SPARC_JMP_SLOT;
   relativeRel = R_SPARC_RELATIVE;
   symbolicRel = R_SPARC_64;
+  tlsGotRel = R_SPARC_TLS_TPOFF64;
   gotHeaderEntriesNum = 1;
   pltEntrySize = 32;
   pltHeaderSize = 4 * pltEntrySize;
@@ -91,6 +92,9 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
     RelExpr expr;
     switch (type) {
     case R_SPARC_NONE:
+    case R_SPARC_TLS_IE_LD:
+    case R_SPARC_TLS_IE_LDX:
+    case R_SPARC_TLS_IE_ADD:
       continue;
 
     // Absolute relocations:
@@ -148,6 +152,11 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
         continue;
       expr = R_TPREL;
       break;
+    case R_SPARC_TLS_IE_HI22:
+    case R_SPARC_TLS_IE_LO10:
+      sym.setFlags(NEEDS_TLSIE);
+      sec.addReloc({R_GOT, type, offset, addend, &sym});
+      continue;
 
     default:
       Err(ctx) << getErrorLoc(ctx, sec.content().data() + offset)
@@ -218,6 +227,7 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
     break;
   case R_SPARC_GOT22:
   case R_SPARC_LM22:
+  case R_SPARC_TLS_IE_HI22:
     // T-imm22
     write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 10) & 0x003fffff));
     break;
@@ -244,6 +254,7 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
     break;
   case R_SPARC_GOT10:
   case R_SPARC_PC10:
+  case R_SPARC_TLS_IE_LO10:
     // T-simm10
     write32be(loc, (read32be(loc) & ~0x000003ff) | (val & 0x000003ff));
     break;
@@ -283,6 +294,10 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
     // T-imm13
     write32be(loc, (read32be(loc) & ~0x00000fff) | (val & 0x00000fff));
     break;
+  case R_SPARC_TLS_IE_LD:
+  case R_SPARC_TLS_IE_LDX:
+  case R_SPARC_TLS_IE_ADD:
+    break;
   case R_SPARC_TLS_LE_HIX22:
     // T-imm22
     write32be(loc, (read32be(loc) & ~0x003fffff) | ((~val >> 10) & 0x003fffff));
diff --git a/lld/test/ELF/sparcv9-tls-ie.s b/lld/test/ELF/sparcv9-tls-ie.s
new file mode 100644
index 0000000000000..541c6beb3576c
--- /dev/null
+++ b/lld/test/ELF/sparcv9-tls-ie.s
@@ -0,0 +1,26 @@
+# REQUIRES: sparc
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 %s -o %t.o
+# RUN: ld.lld -shared %t.o -o %t.so
+# RUN: llvm-readelf -r %t.so | FileCheck %s
+# RUN: llvm-objdump -d --no-show-raw-insn %t.so | FileCheck --check-prefix=DISASM %s
+
+# CHECK: R_SPARC_TLS_TPOFF64 {{.*}} b
+
+# DISASM-LABEL: <_start>:
+# DISASM:       sethi {{.*}}, %i3
+# DISASM-NEXT:  add %i3, {{.*}}, %i3
+# DISASM-NEXT:  ldx [%i0+%i3], %i3
+# DISASM-NEXT:  add %i3, %g7, %i3
+
+.text
+.globl _start
+_start:
+  sethi %tie_hi22(b), %i3
+  add %i3, %tie_lo10(b), %i3
+  ldx [%i0 + %i3], %i3, %tie_ldx(b)
+  add %i3, %g7, %i3, %tie_add(b)
+
+.section .tbss,"awT", at nobits
+.globl b
+b:
+  .zero 8

>From d63711758e5e05210331e799b80fb3f3da1f7f08 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 9 Jul 2026 16:47:30 +0200
Subject: [PATCH 5/7] [lld][ELF][SPARC] Normalize aligned UA64 dynamic
 relocations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: LemonBoy <thatlemon at gmail.com>
Co-authored-by: Alex Rønne Petersen <alex at alexrp.com>
---
 lld/ELF/Arch/SPARCV9.cpp           |  7 +++++
 lld/ELF/Relocations.cpp            | 10 ++++++++
 lld/test/ELF/sparcv9-ua64-dynrel.s | 41 ++++++++++++++++++++++++++++++
 3 files changed, 58 insertions(+)
 create mode 100644 lld/test/ELF/sparcv9-ua64-dynrel.s

diff --git a/lld/ELF/Arch/SPARCV9.cpp b/lld/ELF/Arch/SPARCV9.cpp
index 641bc1a670426..4cf330dd96edb 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -25,6 +25,7 @@ class SPARCV9 final : public TargetInfo {
   SPARCV9(Ctx &);
   RelExpr getRelExpr(RelType type, const Symbol &s,
                      const uint8_t *loc) const override;
+  RelType getDynRel(RelType type) const override;
   void writeGotHeader(uint8_t *buf) const override;
   void writePlt(uint8_t *buf, const Symbol &sym,
                 uint64_t pltEntryAddr) const override;
@@ -75,6 +76,12 @@ RelExpr SPARCV9::getRelExpr(RelType type, const Symbol &s,
   }
 }
 
+RelType SPARCV9::getDynRel(RelType type) const {
+  if (type == symbolicRel || type == R_SPARC_UA64)
+    return type;
+  return R_SPARC_NONE;
+}
+
 template <class ELFT, class RelTy>
 void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
   RelocScan rs(ctx, &sec);
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 0d45236e6d11e..b0dd65f5c11a7 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -991,6 +991,16 @@ void RelocScan::processAux(RelExpr expr, RelType type, uint64_t offset,
                     (isa<EhInputSection>(sec) && ctx.arg.emachine != EM_MIPS));
   if (canWrite) {
     RelType rel = ctx.target->getDynRel(type);
+    if (ctx.arg.emachine == EM_SPARCV9 && type == R_SPARC_UA64) {
+      if (sec->addralign >= 8 && offset % 8 == 0) {
+        rel = R_SPARC_64;
+      } else if (!sym.isPreemptible && !isIfunc) {
+        Err(ctx) << "R_SPARC_UA64 relocation at offset " << offset
+                 << " against non-preemptible symbol " << &sym
+                 << " is not 8-byte aligned";
+        return;
+      }
+    }
     if (oneof<R_GOT, RE_LOONGARCH_GOT>(expr) ||
         ((rel == ctx.target->symbolicRel ||
           (ctx.arg.emachine == EM_AARCH64 && type == R_AARCH64_AUTH_ABS64)) &&
diff --git a/lld/test/ELF/sparcv9-ua64-dynrel.s b/lld/test/ELF/sparcv9-ua64-dynrel.s
new file mode 100644
index 0000000000000..bf50536c9e369
--- /dev/null
+++ b/lld/test/ELF/sparcv9-ua64-dynrel.s
@@ -0,0 +1,41 @@
+# REQUIRES: sparc
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 %s -o %t.o
+# RUN: ld.lld -shared %t.o -o %t.so
+# RUN: llvm-readelf -r %t.so | FileCheck %s
+
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 --defsym ERR=1 %s -o %t.err.o
+# RUN: not ld.lld -shared %t.err.o -o /dev/null 2>&1 | FileCheck --check-prefix=ERR %s
+
+# CHECK:      R_SPARC_RELATIVE
+# CHECK:      R_SPARC_64 {{.*}} external
+# CHECK:      R_SPARC_UA64 {{.*}} external
+
+# ERR: error: R_SPARC_UA64 relocation at offset {{[0-9]+}} against non-preemptible symbol local is not 8-byte aligned
+
+.data
+.p2align 3
+aligned_local:
+  .xword 0
+  .reloc aligned_local, R_SPARC_UA64, local
+
+.p2align 3
+aligned_external:
+  .xword 0
+  .reloc aligned_external, R_SPARC_UA64, external
+
+  .byte 0
+unaligned_external:
+  .xword 0
+  .reloc unaligned_external, R_SPARC_UA64, external
+
+.ifdef ERR
+.p2align 3
+  .byte 0
+unaligned_local:
+  .xword 0
+  .reloc unaligned_local, R_SPARC_UA64, local
+.endif
+
+.hidden local
+local:
+  .xword 0

>From bc485b0d3c03574862fd65a58d57b2b3fc3c0c9e Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 9 Jul 2026 16:47:41 +0200
Subject: [PATCH 6/7] [lld][ELF][SPARC] Handle GOTDATA relocations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: LemonBoy <thatlemon at gmail.com>
Co-authored-by: Alex Rønne Petersen <alex at alexrp.com>
---
 lld/ELF/Arch/SPARCV9.cpp       | 22 ++++++++++++++++++++++
 lld/test/ELF/sparcv9-gotdata.s | 20 ++++++++++++++++++++
 2 files changed, 42 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 4cf330dd96edb..5eaa61b348c7c 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -149,8 +149,15 @@ void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
     case R_SPARC_GOT10:
     case R_SPARC_GOT13:
     case R_SPARC_GOT22:
+    case R_SPARC_GOTDATA_OP_HIX22:
+    case R_SPARC_GOTDATA_OP_LOX10:
+    case R_SPARC_GOTDATA_OP:
       expr = R_GOT_OFF;
       break;
+    case R_SPARC_GOTDATA_HIX22:
+    case R_SPARC_GOTDATA_LOX10:
+      expr = R_GOTREL;
+      break;
 
     // TLS LE relocations:
     case R_SPARC_TLS_LE_HIX22:
@@ -311,9 +318,24 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
     break;
   case R_SPARC_LOX10:
   case R_SPARC_TLS_LE_LOX10:
+  case R_SPARC_GOTDATA_LOX10:
+  case R_SPARC_GOTDATA_OP_LOX10:
     // T-simm13
     write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) | 0x1C00);
     break;
+  case R_SPARC_GOTDATA_HIX22:
+    // V-imm22
+    checkUInt(ctx, loc, ((int64_t)val < 0 ? ~val : val), 32, rel);
+    write32be(loc, (read32be(loc) & ~0x003fffff) |
+                       ((((int64_t)val < 0 ? ~val : val) >> 10) & 0x003fffff));
+    break;
+  case R_SPARC_GOTDATA_OP_HIX22:
+    // T-imm22
+    write32be(loc, (read32be(loc) & ~0x003fffff) |
+                       ((((int64_t)val < 0 ? ~val : val) >> 10) & 0x003fffff));
+    break;
+  case R_SPARC_GOTDATA_OP:
+    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..e8cdb3be20670
--- /dev/null
+++ b/lld/test/ELF/sparcv9-gotdata.s
@@ -0,0 +1,20 @@
+# REQUIRES: sparc
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 %s -o %t.o
+# RUN: ld.lld -shared %t.o -o %t.so
+# RUN: llvm-objdump -d --no-show-raw-insn %t.so | FileCheck %s
+# RUN: llvm-readelf -r %t.so | FileCheck --check-prefix=REL %s
+
+# CHECK-LABEL: <extern_ref>:
+# CHECK:       ldx [%l7+%l1], %l2
+
+# REL: R_SPARC_GLOB_DAT {{.*}} extern
+
+.text
+.globl extern_ref
+.type extern_ref, at function
+extern_ref:
+  sethi %gdop_hix22(extern), %l1
+  or %l1, %gdop_lox10(extern), %l1
+  ldx [%l7 + %l1], %l2, %gdop(extern)
+  retl
+   nop

>From bb96a46113df32c8ea30c96f455dbb79f03ee99f Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 9 Jul 2026 16:47:51 +0200
Subject: [PATCH 7/7] [lld][ELF][SPARC] Relax local GOTDATA_OP relocations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Co-authored-by: LemonBoy <thatlemon at gmail.com>
Co-authored-by: Alex Rønne Petersen <alex at alexrp.com>
---
 lld/ELF/Arch/SPARCV9.cpp             | 50 ++++++++++++++++++++++++++++
 lld/ELF/Relocations.cpp              | 18 ++++++----
 lld/ELF/Target.cpp                   |  6 ++++
 lld/ELF/Target.h                     |  2 ++
 lld/test/ELF/sparcv9-gotdata-relax.s | 25 ++++++++++++++
 5 files changed, 95 insertions(+), 6 deletions(-)
 create mode 100644 lld/test/ELF/sparcv9-gotdata-relax.s

diff --git a/lld/ELF/Arch/SPARCV9.cpp b/lld/ELF/Arch/SPARCV9.cpp
index 5eaa61b348c7c..fd6447ba4df5a 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -36,6 +36,11 @@ class SPARCV9 final : public TargetInfo {
   }
   void relocate(uint8_t *loc, const Relocation &rel,
                 uint64_t val) const override;
+  RelExpr adjustGotOffExpr(RelType type, const Symbol &sym, int64_t addend,
+                           const uint8_t *loc) const override;
+
+private:
+  void relaxGot(uint8_t *loc, const Relocation &rel, uint64_t val) const;
 };
 } // namespace
 
@@ -319,7 +324,12 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
   case R_SPARC_LOX10:
   case R_SPARC_TLS_LE_LOX10:
   case R_SPARC_GOTDATA_LOX10:
+    // T-simm13
+    write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) | 0x1C00);
+    break;
   case R_SPARC_GOTDATA_OP_LOX10:
+    if (rel.expr == R_GOTREL)
+      return relaxGot(loc, rel, val);
     // T-simm13
     write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) | 0x1C00);
     break;
@@ -330,11 +340,51 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
                        ((((int64_t)val < 0 ? ~val : val) >> 10) & 0x003fffff));
     break;
   case R_SPARC_GOTDATA_OP_HIX22:
+    if (rel.expr == R_GOTREL)
+      return relaxGot(loc, rel, val);
     // T-imm22
     write32be(loc, (read32be(loc) & ~0x003fffff) |
                        ((((int64_t)val < 0 ? ~val : val) >> 10) & 0x003fffff));
     break;
   case R_SPARC_GOTDATA_OP:
+    if (rel.expr == R_GOTREL)
+      return relaxGot(loc, rel, val);
+    break;
+  default:
+    llvm_unreachable("unknown relocation");
+  }
+}
+
+RelExpr SPARCV9::adjustGotOffExpr(RelType type, const Symbol &sym,
+                                  int64_t addend, const uint8_t *loc) const {
+  switch (type) {
+  case R_SPARC_GOTDATA_OP_HIX22:
+  case R_SPARC_GOTDATA_OP_LOX10:
+  case R_SPARC_GOTDATA_OP:
+    if (sym.isLocal())
+      return R_GOTREL;
+    [[fallthrough]];
+  default:
+    return R_GOT_OFF;
+  }
+}
+
+void SPARCV9::relaxGot(uint8_t *loc, const Relocation &rel,
+                       uint64_t val) const {
+  switch (rel.type) {
+  case R_SPARC_GOTDATA_OP_HIX22:
+    // T-imm22
+    write32be(loc, (read32be(loc) & ~0x003fffff) |
+                       ((((int64_t)val < 0 ? ~val : val) >> 10) & 0x003fffff));
+    break;
+  case R_SPARC_GOTDATA_OP_LOX10:
+    // T-imm13
+    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
+    write32be(loc, (read32be(loc) & 0x3e07c01f) | 0x80000000);
     break;
   default:
     llvm_unreachable("unknown relocation");
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index b0dd65f5c11a7..30925910af90a 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -920,14 +920,20 @@ void RelocScan::process(RelExpr expr, RelType type, uint64_t offset,
   // indirection.
   const bool isIfunc = sym.isGnuIFunc();
   if (!sym.isPreemptible && !isIfunc) {
-    if (expr != R_GOT_PC) {
+    if (expr != R_GOT_PC && expr != R_GOT_OFF) {
       expr = fromPlt(expr);
     } else if (!isAbsoluteOrTls(sym)) {
-      expr = ctx.target->adjustGotPcExpr(type, addend,
-                                         sec->content().data() + offset);
-      // If the target adjusted the expression to R_RELAX_GOT_PC, we may end up
-      // needing the GOT if we can't relax everything.
-      if (expr == R_RELAX_GOT_PC)
+      if (expr == R_GOT_PC)
+        expr = ctx.target->adjustGotPcExpr(type, addend,
+                                           sec->content().data() + offset);
+      else
+        expr = ctx.target->adjustGotOffExpr(type, sym, addend,
+                                            sec->content().data() + offset);
+
+      // If the target adjusted the expression to a GOT-relative relaxation, we
+      // may end up needing the GOT if we can't relax everything.
+      if (expr == R_RELAX_GOT_PC ||
+          (ctx.arg.emachine == EM_SPARCV9 && expr == R_GOTREL))
         ctx.in.got->hasGotOffRel.store(true, std::memory_order_relaxed);
     }
   }
diff --git a/lld/ELF/Target.cpp b/lld/ELF/Target.cpp
index 89e4dbeed3109..6ad4904d2546a 100644
--- a/lld/ELF/Target.cpp
+++ b/lld/ELF/Target.cpp
@@ -149,6 +149,12 @@ RelExpr TargetInfo::adjustGotPcExpr(RelType type, int64_t addend,
   return R_GOT_PC;
 }
 
+RelExpr TargetInfo::adjustGotOffExpr(RelType type, const Symbol &sym,
+                                     int64_t addend,
+                                     const uint8_t *data) const {
+  return R_GOT_OFF;
+}
+
 static void relocateImpl(const TargetInfo &target, InputSectionBase &sec,
                          uint64_t secAddr, uint8_t *buf) {
   auto &ctx = target.ctx;
diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h
index 199f76e3fe851..70a63f4f0829a 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -195,6 +195,8 @@ class TargetInfo {
   virtual RelExpr adjustTlsExpr(RelType type, RelExpr expr) const;
   virtual RelExpr adjustGotPcExpr(RelType type, int64_t addend,
                                   const uint8_t *loc) const;
+  virtual RelExpr adjustGotOffExpr(RelType type, const Symbol &sym,
+                                   int64_t addend, const uint8_t *loc) const;
 
 protected:
   // On FreeBSD x86_64 the first page cannot be mmaped.
diff --git a/lld/test/ELF/sparcv9-gotdata-relax.s b/lld/test/ELF/sparcv9-gotdata-relax.s
new file mode 100644
index 0000000000000..b94ea94528c21
--- /dev/null
+++ b/lld/test/ELF/sparcv9-gotdata-relax.s
@@ -0,0 +1,25 @@
+# REQUIRES: sparc
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 %s -o %t.o
+# RUN: ld.lld -shared %t.o -o %t.so
+# RUN: llvm-objdump -d --no-show-raw-insn %t.so | FileCheck %s
+# RUN: llvm-readelf -r %t.so | FileCheck --check-prefix=REL %s
+
+# CHECK-LABEL: <local_ref>:
+# CHECK:       add %l7, %l1, %l2
+
+# REL-NOT: local
+
+.text
+.globl local_ref
+.type local_ref, at function
+local_ref:
+  sethi %gdop_hix22(local), %l1
+  or %l1, %gdop_lox10(local), %l1
+  ldx [%l7 + %l1], %l2, %gdop(local)
+  retl
+   nop
+
+.data
+.hidden local
+local:
+  .xword 0



More information about the llvm-commits mailing list