[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 16 14:54:08 PDT 2026


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

>From 286278db7e974207879e2a46a8562ea3c69e7d20 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 16 Jul 2026 23:51:25 +0200
Subject: [PATCH 01/10] [lld][ELF][SPARC] Use RELA relocations for SPARCV9

Co-authored-by: LemonBoy <thatlemon at gmail.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 77821e8c813ef..d4471dba85921 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 09189f4ab2ed141f122ef9393d694ddb02ac65ee Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 16 Jul 2026 23:51:34 +0200
Subject: [PATCH 02/10] [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                | 83 ++++++++++++++++++++++---
 lld/test/ELF/sparcv9-reloc-additional.s | 67 ++++++++++++++++++++
 2 files changed, 140 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 fad83da0d397d..2e0aa417da4aa 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -57,7 +57,9 @@ SPARCV9::SPARCV9(Ctx &ctx) : TargetInfo(ctx) {
 RelExpr SPARCV9::getRelExpr(RelType type, const Symbol &s,
                             const uint8_t *loc) const {
   switch (type) {
+  case R_SPARC_16:
   case R_SPARC_32:
+  case R_SPARC_UA16:
   case R_SPARC_UA32:
   case R_SPARC_64:
   case R_SPARC_UA64:
@@ -94,10 +96,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:
@@ -105,7 +110,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;
 
@@ -117,13 +125,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;
@@ -149,6 +164,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
@@ -157,36 +183,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
@@ -194,25 +251,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:
@@ -221,12 +283,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 76a2b74c6bcefced448a1ffbf9a64209f982a22d Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 16 Jul 2026 23:51:44 +0200
Subject: [PATCH 03/10] [lld][ELF][SPARC] Add SPARC GOT header entry

Co-authored-by: LemonBoy <thatlemon at gmail.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 2e0aa417da4aa..fe85fd8c856b9 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>
@@ -44,6 +45,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;
 
@@ -299,6 +301,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 e85916f3ff6467be570e8367d2231c73e1ab060c Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 16 Jul 2026 23:51:53 +0200
Subject: [PATCH 04/10] [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 fe85fd8c856b9..edccc8a49356a 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -45,6 +45,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;
@@ -95,6 +96,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:
@@ -152,6 +156,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)
@@ -222,6 +231,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;
@@ -248,6 +258,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;
@@ -287,6 +298,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 30d82e0228c16839f95fe5897cbb164b335b1ca4 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 16 Jul 2026 23:52:03 +0200
Subject: [PATCH 05/10] [lld][ELF][SPARC] Canonicalize absolute dynamic
 relocations

Co-authored-by: LemonBoy <thatlemon at gmail.com>
---
 lld/ELF/Arch/SPARCV9.cpp         | 63 +++++++++++++++++++++++++
 lld/ELF/Relocations.cpp          | 13 ++++--
 lld/ELF/SyntheticSections.cpp    |  1 +
 lld/ELF/SyntheticSections.h      |  5 ++
 lld/ELF/Target.h                 |  2 +
 lld/test/ELF/sparcv9-ua-dynrel.s | 80 ++++++++++++++++++++++++++++++++
 6 files changed, 160 insertions(+), 4 deletions(-)
 create mode 100644 lld/test/ELF/sparcv9-ua-dynrel.s

diff --git a/lld/ELF/Arch/SPARCV9.cpp b/lld/ELF/Arch/SPARCV9.cpp
index edccc8a49356a..be310d72183b6 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -25,6 +25,8 @@ 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 finalizeDynamicReloc(DynamicReloc &rel) const override;
   void writeGotHeader(uint8_t *buf) const override;
   void writePlt(uint8_t *buf, const Symbol &sym,
                 uint64_t pltEntryAddr) const override;
@@ -78,6 +80,67 @@ RelExpr SPARCV9::getRelExpr(RelType type, const Symbol &s,
   }
 }
 
+RelType SPARCV9::getDynRel(RelType type) const {
+  switch (type) {
+  case R_SPARC_16:
+  case R_SPARC_32:
+  case R_SPARC_64:
+  case R_SPARC_UA16:
+  case R_SPARC_UA32:
+  case R_SPARC_UA64:
+    return type;
+  default:
+    break;
+  }
+  return R_SPARC_NONE;
+}
+
+static bool getSparcAbsRelocPair(RelType type, RelType &aligned,
+                                 RelType &unaligned, uint64_t &alignment) {
+  switch (type) {
+  case R_SPARC_16:
+  case R_SPARC_UA16:
+    aligned = R_SPARC_16;
+    unaligned = R_SPARC_UA16;
+    alignment = 2;
+    return true;
+  case R_SPARC_32:
+  case R_SPARC_UA32:
+    aligned = R_SPARC_32;
+    unaligned = R_SPARC_UA32;
+    alignment = 4;
+    return true;
+  case R_SPARC_64:
+  case R_SPARC_UA64:
+    aligned = R_SPARC_64;
+    unaligned = R_SPARC_UA64;
+    alignment = 8;
+    return true;
+  default:
+    return false;
+  }
+}
+
+void SPARCV9::finalizeDynamicReloc(DynamicReloc &rel) const {
+  RelType aligned = R_SPARC_NONE, unaligned = R_SPARC_NONE;
+  uint64_t alignment = 1;
+  if (!getSparcAbsRelocPair(rel.type, aligned, unaligned, alignment))
+    return;
+
+  rel.type = rel.r_offset % alignment == 0 ? aligned : unaligned;
+  if (!rel.needsDynSymIndex() || rel.sym->isPreemptible)
+    return;
+
+  if (rel.type == R_SPARC_64) {
+    rel.convertToRelative(relativeRel);
+    return;
+  }
+
+  Err(ctx) << "relocation " << rel.type << " at offset " << rel.r_offset
+           << " against non-preemptible symbol " << rel.sym
+           << " cannot be converted to " << relativeRel;
+}
+
 template <class ELFT, class RelTy>
 void SPARCV9::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels,
                               unsigned shard) {
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 4702d941d28ca..6509f3cad97d0 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -993,10 +993,15 @@ 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 (oneof<R_GOT, RE_LOONGARCH_GOT>(expr) ||
-        ((rel == ctx.target->symbolicRel ||
-          (ctx.arg.emachine == EM_AARCH64 && type == R_AARCH64_AUTH_ABS64)) &&
-         !sym.isPreemptible)) {
+    bool useRelative =
+        (rel == ctx.target->symbolicRel ||
+         (ctx.arg.emachine == EM_AARCH64 && type == R_AARCH64_AUTH_ABS64)) &&
+        !sym.isPreemptible;
+    if (ctx.arg.emachine == EM_SPARCV9 &&
+        (type == R_SPARC_16 || type == R_SPARC_32 || type == R_SPARC_64 ||
+         type == R_SPARC_UA16 || type == R_SPARC_UA32 || type == R_SPARC_UA64))
+      useRelative = false;
+    if (oneof<R_GOT, RE_LOONGARCH_GOT>(expr) || useRelative) {
       addRelativeReloc<true>(ctx, *sec, offset, sym, addend, expr, type, shard);
       return;
     }
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index fda7a835827ce..f272ef0af77c0 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -1529,6 +1529,7 @@ void RelocationBaseSection::finalizeContents() {
 
 void DynamicReloc::finalize(Ctx &ctx, SymbolTableBaseSection *symt) {
   r_offset = getOffset();
+  ctx.target->finalizeDynamicReloc(*this);
   r_sym = getSymIndex(symt);
   addend = computeAddend(ctx);
   isFinal = true; // Catch errors
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index 6e7df2461ba35..0b0930b4c5d42 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -439,6 +439,11 @@ class DynamicReloc {
   uint64_t getOffset() const;
   uint32_t getSymIndex(SymbolTableBaseSection *symTab) const;
   bool needsDynSymIndex() const { return isAgainstSymbol; }
+  void convertToRelative(RelType relativeRel) {
+    type = relativeRel;
+    isAgainstSymbol = false;
+    expr = R_ABS;
+  }
 
   /// Computes the addend of the dynamic relocation. Note that this is not the
   /// same as the #addend member variable as it may also include the symbol
diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h
index 50f0cd8a92161..f72e1c8b76aef 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -22,6 +22,7 @@
 namespace lld {
 namespace elf {
 class Defined;
+class DynamicReloc;
 class InputFile;
 class Symbol;
 template <class RelTy> struct Relocs;
@@ -37,6 +38,7 @@ class TargetInfo {
   virtual RelExpr getRelExpr(RelType type, const Symbol &s,
                              const uint8_t *loc) const = 0;
   virtual RelType getDynRel(RelType type) const { return 0; }
+  virtual void finalizeDynamicReloc(DynamicReloc &rel) const {}
   virtual void writeGotPltHeader(uint8_t *buf) const {}
   virtual void writeGotHeader(uint8_t *buf) const {}
   virtual void writeGotPlt(uint8_t *buf, const Symbol &s) const {}
diff --git a/lld/test/ELF/sparcv9-ua-dynrel.s b/lld/test/ELF/sparcv9-ua-dynrel.s
new file mode 100644
index 0000000000000..4f58f95852312
--- /dev/null
+++ b/lld/test/ELF/sparcv9-ua-dynrel.s
@@ -0,0 +1,80 @@
+# 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_16 {{.*}} external
+# CHECK:      R_SPARC_32 {{.*}} external
+# CHECK:      R_SPARC_64 {{.*}} external
+# CHECK:      R_SPARC_UA16 {{.*}} external
+# CHECK:      R_SPARC_UA32 {{.*}} external
+# CHECK:      R_SPARC_UA64 {{.*}} external
+
+# ERR-DAG: error: relocation R_SPARC_16 at offset {{[0-9]+}} against non-preemptible symbol local cannot be converted to R_SPARC_RELATIVE
+# ERR-DAG: error: relocation R_SPARC_32 at offset {{[0-9]+}} against non-preemptible symbol local cannot be converted to R_SPARC_RELATIVE
+# ERR-DAG: error: relocation R_SPARC_UA64 at offset {{[0-9]+}} against non-preemptible symbol local cannot be converted to R_SPARC_RELATIVE
+
+.data
+.p2align 3
+aligned_local64:
+  .xword 0
+  .reloc aligned_local64, R_SPARC_UA64, local
+
+.p2align 1
+aligned_external16:
+  .half 0
+  .reloc aligned_external16, R_SPARC_UA16, external
+
+.p2align 2
+aligned_external32:
+  .word 0
+  .reloc aligned_external32, R_SPARC_UA32, external
+
+.p2align 3
+aligned_external64:
+  .xword 0
+  .reloc aligned_external64, R_SPARC_UA64, external
+
+.p2align 1
+  .byte 0
+unaligned_external16:
+  .half 0
+  .reloc unaligned_external16, R_SPARC_16, external
+
+.p2align 2
+  .byte 0
+unaligned_external32:
+  .word 0
+  .reloc unaligned_external32, R_SPARC_32, external
+
+.p2align 3
+  .byte 0
+unaligned_external64:
+  .xword 0
+  .reloc unaligned_external64, R_SPARC_64, external
+
+.ifdef ERR
+.p2align 1
+aligned_local16:
+  .half 0
+  .reloc aligned_local16, R_SPARC_UA16, local
+
+.p2align 2
+aligned_local32:
+  .word 0
+  .reloc aligned_local32, R_SPARC_UA32, local
+
+.p2align 3
+  .byte 0
+unaligned_local64:
+  .xword 0
+  .reloc unaligned_local64, R_SPARC_64, local
+.endif
+
+.hidden local
+local:
+  .xword 0

>From 0cb2bfafc48f911e6ae111d65e654c91b193f35c Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 16 Jul 2026 23:52:13 +0200
Subject: [PATCH 06/10] [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 be310d72183b6..c15f064e08d6b 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -209,8 +209,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:
@@ -371,9 +378,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 3e72ce1c2674cb0884481284ecfb71190bf153fb Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 16 Jul 2026 23:52:23 +0200
Subject: [PATCH 07/10] [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 c15f064e08d6b..b39a22dda3e9d 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -38,6 +38,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
 
@@ -379,7 +384,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;
@@ -390,11 +400,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 6509f3cad97d0..4710dbe65270e 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -922,14 +922,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 f72e1c8b76aef..b667c838407be 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -198,6 +198,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

>From a8187229246eedd4617b0784897f128e4b08621c Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 16 Jul 2026 23:52:33 +0200
Subject: [PATCH 08/10] [lld][ELF] Preserve writable .eh_frame inputs

The synthetic .eh_frame section is created with SHF_ALLOC and drops
SHF_WRITE from input sections. Propagate SHF_WRITE while combining
inputs so the output remains writable when required by its relocations.
---
 lld/ELF/SyntheticSections.cpp    |  1 +
 lld/test/ELF/eh-frame-write.test | 17 +++++++++++++++++
 2 files changed, 18 insertions(+)
 create mode 100644 lld/test/ELF/eh-frame-write.test

diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index f272ef0af77c0..3021dce3dc1de 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -3837,6 +3837,7 @@ void elf::combineEhSections(Ctx &ctx) {
     EhFrameSection &eh = *ctx.in.ehFrame;
     sec->parent = &eh;
     eh.addralign = std::max(eh.addralign, sec->addralign);
+    eh.flags |= sec->flags & SHF_WRITE;
     eh.sections.push_back(sec);
     llvm::append_range(eh.dependentSections, sec->dependentSections);
   }
diff --git a/lld/test/ELF/eh-frame-write.test b/lld/test/ELF/eh-frame-write.test
new file mode 100644
index 0000000000000..00a58b7c905d9
--- /dev/null
+++ b/lld/test/ELF/eh-frame-write.test
@@ -0,0 +1,17 @@
+# RUN: yaml2obj %s -o %t.o
+# RUN: ld.lld -shared %t.o -o %t.so
+# RUN: llvm-readelf -SW %t.so | FileCheck %s
+
+# CHECK: .eh_frame PROGBITS {{.*}} 000004 00  WA  0   0  1
+
+--- !ELF
+FileHeader:
+  Class:   ELFCLASS64
+  Data:    ELFDATA2LSB
+  Type:    ET_REL
+  Machine: EM_X86_64
+Sections:
+  - Name:    .eh_frame
+    Type:    SHT_PROGBITS
+    Flags:   [ SHF_ALLOC, SHF_WRITE ]
+    Content: "00000000"

>From b3cebfc319d22d62ffc4a1e4965b4cbcfacd07d6 Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 16 Jul 2026 23:52:42 +0200
Subject: [PATCH 09/10] [lld][ELF] Fix dynamic relocations in .eh_frame

scanEhSection translates input offsets to merged .eh_frame offsets
before creating dynamic relocations. DynamicReloc::getOffset translated
them a second time through EhInputSection.

Use the synthetic parent for already merged offsets, and use the
finalized relocation offset when verifying applied addends.
---
 lld/ELF/OutputSections.cpp        |  4 ++--
 lld/ELF/Relocations.cpp           |  4 ++--
 lld/ELF/SyntheticSections.cpp     |  2 ++
 lld/test/ELF/eh-frame-dyn-reloc.s | 33 +++++++++++++++++++++++++++++++
 4 files changed, 39 insertions(+), 4 deletions(-)
 create mode 100644 lld/test/ELF/eh-frame-dyn-reloc.s

diff --git a/lld/ELF/OutputSections.cpp b/lld/ELF/OutputSections.cpp
index eb1d8af5f375b..d3dcd6a5b77e9 100644
--- a/lld/ELF/OutputSections.cpp
+++ b/lld/ELF/OutputSections.cpp
@@ -924,8 +924,8 @@ void OutputSection::checkDynRelAddends(Ctx &ctx) {
           (rel.inputSec == ctx.in.ppc64LongBranchTarget.get() ||
            rel.inputSec == ctx.in.igotPlt.get()))
         continue;
-      const uint8_t *relocTarget = ctx.bufferStart + relOsec->offset +
-                                   rel.inputSec->getOffset(rel.offsetInSec);
+      const uint8_t *relocTarget =
+          ctx.bufferStart + relOsec->offset + (rel.r_offset - relOsec->addr);
       // For SHT_NOBITS the written addend is always zero.
       int64_t writtenAddend =
           relOsec->type == SHT_NOBITS
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 4710dbe65270e..7a808a223c9eb 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -989,8 +989,8 @@ void RelocScan::processAux(RelExpr expr, RelType type, uint64_t offset,
   }
 
   // Use a simple -z notext rule that treats all sections except .eh_frame as
-  // writable. GNU ld does not produce dynamic relocations in .eh_frame (and our
-  // SectionBase::getOffset would incorrectly adjust the offset).
+  // writable. GNU ld does not produce dynamic relocations in read-only
+  // .eh_frame.
   //
   // For MIPS, we don't implement GNU ld's DW_EH_PE_absptr to DW_EH_PE_pcrel
   // conversion. We still emit a dynamic relocation.
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index 3021dce3dc1de..ac4e2aa84a3f9 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -1440,6 +1440,8 @@ template <class ELFT> void DynamicSection<ELFT>::writeTo(uint8_t *buf) {
 }
 
 uint64_t DynamicReloc::getOffset() const {
+  if (auto *eh = dyn_cast<EhInputSection>(inputSec))
+    return eh->getParent()->getVA(offsetInSec);
   return inputSec->getVA(offsetInSec);
 }
 
diff --git a/lld/test/ELF/eh-frame-dyn-reloc.s b/lld/test/ELF/eh-frame-dyn-reloc.s
new file mode 100644
index 0000000000000..55a104da86f91
--- /dev/null
+++ b/lld/test/ELF/eh-frame-dyn-reloc.s
@@ -0,0 +1,33 @@
+# REQUIRES: x86
+# RUN: rm -rf %t && split-file %s %t
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/first.s -o %t/first-ro.o
+# RUN: llvm-objcopy --set-section-flags .eh_frame=alloc,data \
+# RUN:   %t/first-ro.o %t/first.o
+# RUN: llvm-mc -filetype=obj -triple=x86_64 %t/second.s -o %t/second-ro.o
+# RUN: llvm-objcopy --set-section-flags .eh_frame=alloc,data \
+# RUN:   %t/second-ro.o %t/second.o
+# RUN: ld.lld -shared --apply-dynamic-relocs --check-dynamic-relocations \
+# RUN:   %t/first.o %t/second.o -o %t/out.so
+# RUN: llvm-readelf -S -r %t/out.so | FileCheck %s
+
+# CHECK: .eh_frame PROGBITS [[#%x,EH:]]
+# CHECK: {{0*}}[[#%x,EH+0x12]] {{.*}} R_X86_64_64 {{.*}} external1 + 0
+# CHECK: {{0*}}[[#%x,EH+0x4a]] {{.*}} R_X86_64_64 {{.*}} external2 + 0
+
+#--- first.s
+.text
+.globl first
+first:
+  .cfi_startproc
+  .cfi_personality 0, external1
+  ret
+  .cfi_endproc
+
+#--- second.s
+.text
+.globl second
+second:
+  .cfi_startproc
+  .cfi_personality 0, external2
+  ret
+  .cfi_endproc

>From 20b081b8e00405a745f144980a88c9583494266e Mon Sep 17 00:00:00 2001
From: "Kirill A. Korinsky" <kirill at korins.ky>
Date: Thu, 16 Jul 2026 23:52:53 +0200
Subject: [PATCH 10/10] [lld][ELF][SPARC] Support local dynamic relocations

Emit local dynamic section symbols for absolute SPARC relocations that
cannot be represented by R_SPARC_RELATIVE. Keep local symbols before
global symbols and exclude them from GNU and SysV hash tables.

SPARC loaders ignore st_value for defined local symbols, so preserve the
full link time target address in the relocation addend. This matches GNU
BFD and glibc behavior.

Create the symbols after parallel relocation scanning to keep their
order deterministic. Test shared objects and static PIE, every alignment
variant, dynamic symbol metadata, and both hash styles.
---
 lld/ELF/Arch/SPARCV9.cpp            |  50 ++++++++++-
 lld/ELF/Relocations.cpp             |   1 +
 lld/ELF/SyntheticSections.cpp       |  18 ++--
 lld/ELF/SyntheticSections.h         |   2 +
 lld/ELF/Target.h                    |   3 +
 lld/test/ELF/sparcv9-local-dynrel.s | 128 ++++++++++++++++++++++++++++
 lld/test/ELF/sparcv9-ua-dynrel.s    |  25 ------
 7 files changed, 193 insertions(+), 34 deletions(-)
 create mode 100644 lld/test/ELF/sparcv9-local-dynrel.s

diff --git a/lld/ELF/Arch/SPARCV9.cpp b/lld/ELF/Arch/SPARCV9.cpp
index b39a22dda3e9d..cfe369b220b64 100644
--- a/lld/ELF/Arch/SPARCV9.cpp
+++ b/lld/ELF/Arch/SPARCV9.cpp
@@ -6,10 +6,13 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "LinkerScript.h"
+#include "OutputSections.h"
 #include "RelocScan.h"
 #include "Symbols.h"
 #include "SyntheticSections.h"
 #include "Target.h"
+#include "llvm/ADT/DenseMap.h"
 #include "llvm/Support/Endian.h"
 
 using namespace llvm;
@@ -26,6 +29,8 @@ class SPARCV9 final : public TargetInfo {
   RelExpr getRelExpr(RelType type, const Symbol &s,
                      const uint8_t *loc) const override;
   RelType getDynRel(RelType type) const override;
+  void prepareDynamicReloc(RelType type, const Symbol &sym) override;
+  void finalizeRelocScan() override;
   void finalizeDynamicReloc(DynamicReloc &rel) const override;
   void writeGotHeader(uint8_t *buf) const override;
   void writePlt(uint8_t *buf, const Symbol &sym,
@@ -43,6 +48,8 @@ class SPARCV9 final : public TargetInfo {
 
 private:
   void relaxGot(uint8_t *loc, const Relocation &rel, uint64_t val) const;
+
+  DenseMap<OutputSection *, Defined *> dynamicSectionSymbols;
 };
 } // namespace
 
@@ -126,6 +133,34 @@ static bool getSparcAbsRelocPair(RelType type, RelType &aligned,
   }
 }
 
+void SPARCV9::prepareDynamicReloc(RelType type, const Symbol &sym) {
+  RelType aligned = R_SPARC_NONE, unaligned = R_SPARC_NONE;
+  uint64_t alignment = 1;
+  if (!getSparcAbsRelocPair(type, aligned, unaligned, alignment) ||
+      sym.isPreemptible)
+    return;
+
+  if (OutputSection *osec = sym.getOutputSection())
+    dynamicSectionSymbols.try_emplace(osec, nullptr);
+}
+
+void SPARCV9::finalizeRelocScan() {
+  for (SectionCommand *cmd : ctx.script->sectionCommands) {
+    auto *osd = dyn_cast<OutputDesc>(cmd);
+    if (!osd)
+      continue;
+
+    auto it = dynamicSectionSymbols.find(&osd->osec);
+    if (it == dynamicSectionSymbols.end())
+      continue;
+
+    Defined *sym = makeDefined(ctx, ctx.internalFile, "", STB_LOCAL,
+                               STV_DEFAULT, STT_SECTION, 0, 0, &osd->osec);
+    ctx.in.dynSymTab->addLocalSectionSymbol(sym);
+    it->second = sym;
+  }
+}
+
 void SPARCV9::finalizeDynamicReloc(DynamicReloc &rel) const {
   RelType aligned = R_SPARC_NONE, unaligned = R_SPARC_NONE;
   uint64_t alignment = 1;
@@ -141,9 +176,18 @@ void SPARCV9::finalizeDynamicReloc(DynamicReloc &rel) const {
     return;
   }
 
-  Err(ctx) << "relocation " << rel.type << " at offset " << rel.r_offset
-           << " against non-preemptible symbol " << rel.sym
-           << " cannot be converted to " << relativeRel;
+  auto it = dynamicSectionSymbols.find(rel.sym->getOutputSection());
+  if (it == dynamicSectionSymbols.end() || !it->second) {
+    Err(ctx) << "relocation " << rel.type << " at offset " << rel.r_offset
+             << " against non-preemptible symbol " << rel.sym
+             << " cannot be converted to " << relativeRel;
+    return;
+  }
+
+  // SPARC dynamic loaders ignore st_value for defined local symbols, so keep
+  // the full link time value in the addend.
+  rel.addend = rel.sym->getVA(ctx, rel.addend);
+  rel.sym = it->second;
 }
 
 template <class ELFT, class RelTy>
diff --git a/lld/ELF/Relocations.cpp b/lld/ELF/Relocations.cpp
index 7a808a223c9eb..97fecc6c931c3 100644
--- a/lld/ELF/Relocations.cpp
+++ b/lld/ELF/Relocations.cpp
@@ -1032,6 +1032,7 @@ void RelocScan::processAux(RelExpr expr, RelType type, uint64_t offset,
           return;
         }
       }
+      ctx.target->prepareDynamicReloc(rel, sym);
       ctx.in.relaDyn->addSymbolReloc(rel, *sec, offset, sym, addend, type);
 
       // MIPS ABI turns using of GOT and dynamic relocations inside out.
diff --git a/lld/ELF/SyntheticSections.cpp b/lld/ELF/SyntheticSections.cpp
index ac4e2aa84a3f9..4b1a4b4b05015 100644
--- a/lld/ELF/SyntheticSections.cpp
+++ b/lld/ELF/SyntheticSections.cpp
@@ -1984,12 +1984,9 @@ void SymbolTableBaseSection::finalizeContents() {
     return;
   }
 
-  // If it is a .dynsym, there should be no local symbols, but we need
-  // to do a few things for the dynamic linker.
-
   // Section's Info field has the index of the first non-local symbol.
-  // Because the first symbol entry is a null entry, 1 is the first.
-  getParent()->info = 1;
+  // Account for the null symbol before the local symbols.
+  getParent()->info = numLocalDynamicSymbols + 1;
 
   if (ctx.in.gnuHashTab) {
     // NB: It also sorts Symbols to meet the GNU hash table requirements.
@@ -2065,6 +2062,13 @@ void SymbolTableBaseSection::addSymbol(Symbol *b) {
   symbols.push_back({b, strTabSec.addString(b->getName(), false)});
 }
 
+void SymbolTableBaseSection::addLocalSectionSymbol(Symbol *b) {
+  assert(this->type == SHT_DYNSYM && b->isLocal() && b->isSection());
+  symbols.insert(symbols.begin() + numLocalDynamicSymbols,
+                 {b, strTabSec.addString(b->getName(), false)});
+  ++numLocalDynamicSymbols;
+}
+
 size_t SymbolTableBaseSection::getSymbolIndex(const Symbol &sym) {
   if (this == ctx.in.dynSymTab.get())
     return sym.dynsymIndex;
@@ -2329,7 +2333,7 @@ void GnuHashTableSection::addSymbols(SmallVectorImpl<SymbolTableEntry> &v) {
   // its type correctly.
   auto mid =
       std::stable_partition(v.begin(), v.end(), [&](const SymbolTableEntry &s) {
-        return !s.sym->isDefined();
+        return s.sym->isLocal() || !s.sym->isDefined();
       });
 
   // We chose load factor 4 for the on-disk hash table. For each hash
@@ -2395,6 +2399,8 @@ void HashTableSection::writeTo(uint8_t *buf) {
 
   for (const SymbolTableEntry &s : symTab->getSymbols()) {
     Symbol *sym = s.sym;
+    if (sym->isLocal())
+      continue;
     StringRef name = sym->getName();
     unsigned i = sym->dynsymIndex;
     uint32_t hash = hashSysV(name) % numSymbols;
diff --git a/lld/ELF/SyntheticSections.h b/lld/ELF/SyntheticSections.h
index 0b0930b4c5d42..e2821067d2aa0 100644
--- a/lld/ELF/SyntheticSections.h
+++ b/lld/ELF/SyntheticSections.h
@@ -664,6 +664,7 @@ class SymbolTableBaseSection : public SyntheticSection {
   void finalizeContents() override;
   size_t getSize() const override { return getNumSymbols() * entsize; }
   void addSymbol(Symbol *sym);
+  void addLocalSectionSymbol(Symbol *sym);
   void maybeAddSttFile();
   void markGlobalPart() { firstGlobalIdx = symbols.size(); }
   unsigned getNumSymbols() const { return symbols.size() + 1; }
@@ -675,6 +676,7 @@ class SymbolTableBaseSection : public SyntheticSection {
 
   // A vector of symbols and their string table offsets.
   SmallVector<SymbolTableEntry, 0> symbols;
+  size_t numLocalDynamicSymbols = 0;
 
   // Synthetic STT_FILE with an empty name, added by maybeAddSttFile and placed
   // by sortSymTabSymbols before all locals that cannot be attributed to a file.
diff --git a/lld/ELF/Target.h b/lld/ELF/Target.h
index b667c838407be..81b6ee83683ab 100644
--- a/lld/ELF/Target.h
+++ b/lld/ELF/Target.h
@@ -38,6 +38,9 @@ class TargetInfo {
   virtual RelExpr getRelExpr(RelType type, const Symbol &s,
                              const uint8_t *loc) const = 0;
   virtual RelType getDynRel(RelType type) const { return 0; }
+  // Called with ctx.relocMutex held before recording a symbol dynamic
+  // relocation.
+  virtual void prepareDynamicReloc(RelType type, const Symbol &sym) {}
   virtual void finalizeDynamicReloc(DynamicReloc &rel) const {}
   virtual void writeGotPltHeader(uint8_t *buf) const {}
   virtual void writeGotHeader(uint8_t *buf) const {}
diff --git a/lld/test/ELF/sparcv9-local-dynrel.s b/lld/test/ELF/sparcv9-local-dynrel.s
new file mode 100644
index 0000000000000..492e335c93b8f
--- /dev/null
+++ b/lld/test/ELF/sparcv9-local-dynrel.s
@@ -0,0 +1,128 @@
+# REQUIRES: sparc
+# RUN: llvm-mc -filetype=obj -triple=sparcv9 %s -o %t.o
+# RUN: ld.lld -shared --hash-style=both -z max-page-size=0x1000 \
+# RUN:   --section-start=.data=0x4000 %t.o -o %t.so
+# RUN: llvm-readelf -SWr --dyn-syms %t.so | \
+# RUN:   FileCheck %s --check-prefixes=RELOC,SHARED
+# RUN: llvm-readobj --hash-table --gnu-hash-table %t.so | \
+# RUN:   FileCheck %s --check-prefix=SHARED-HASH
+# RUN: ld.lld -shared --hash-style=sysv -z max-page-size=0x1000 \
+# RUN:   --section-start=.data=0x4000 %t.o -o %t.sysv.so
+# RUN: llvm-readelf -SW --dyn-syms %t.sysv.so | \
+# RUN:   FileCheck %s --check-prefix=SYSV-ONLY
+# RUN: ld.lld -static -pie --no-dynamic-linker --hash-style=both \
+# RUN:   -z max-page-size=0x1000 --section-start=.data=0x4000 \
+# RUN:   %t.o -o %t.pie
+# RUN: llvm-readelf -SWr --dyn-syms %t.pie | \
+# RUN:   FileCheck %s --check-prefixes=RELOC,STATIC-PIE
+# RUN: llvm-readobj --hash-table --gnu-hash-table %t.pie | \
+# RUN:   FileCheck %s --check-prefix=STATIC-PIE-HASH
+
+# SHARED:    .dynsym DYNSYM {{.*}} 000048 18 A {{[0-9]+}} 2 8
+# STATIC-PIE: .dynsym DYNSYM {{.*}} 000030 18 A {{[0-9]+}} 2 8
+
+# RELOC:      Relocation section '.rela.dyn' at offset {{.*}} contains 6 entries:
+# RELOC-NEXT:     Offset             Info             Type               Symbol's Value  Symbol's Name + Addend
+# RELOC-NEXT: 0000000000004020  0000000000000016 R_SPARC_RELATIVE                         4005
+# RELOC-NEXT: 0000000000004008  0000000100000002 R_SPARC_16       0000000000004000 .data + 4001
+# RELOC-NEXT: 000000000000400b  0000000100000037 R_SPARC_UA16     0000000000004000 .data + 4002
+# RELOC-NEXT: 0000000000004010  0000000100000003 R_SPARC_32       0000000000004000 .data + 4003
+# RELOC-NEXT: 0000000000004015  0000000100000017 R_SPARC_UA32     0000000000004000 .data + 4004
+# RELOC-NEXT: 0000000000004029  0000000100000036 R_SPARC_UA64     0000000000004000 .data + 4006
+
+# SHARED:      Symbol table '.dynsym' contains 3 entries:
+# SHARED-NEXT:    Num:    Value          Size Type    Bind   Vis      Ndx Name
+# SHARED-NEXT:      0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
+# SHARED-NEXT:      1: 0000000000004000     0 SECTION LOCAL  DEFAULT {{[0-9]+}} .data
+# SHARED-NEXT:      2: {{[0-9a-f]+}}     0 FUNC    GLOBAL DEFAULT {{[0-9]+}} exported
+
+# SHARED-HASH:      HashTable {
+# SHARED-HASH-NEXT:   Num Buckets: 3
+# SHARED-HASH-NEXT:   Num Chains: 3
+# SHARED-HASH-NEXT:   Buckets: [2, 0, 0]
+# SHARED-HASH-NEXT:   Chains: [0, 0, 0]
+# SHARED-HASH-NEXT: }
+# SHARED-HASH:      GnuHashTable {
+# SHARED-HASH-NEXT:   Num Buckets: 1
+# SHARED-HASH-NEXT:   First Hashed Symbol Index: 2
+# SHARED-HASH-NEXT:   Num Mask Words: 1
+# SHARED-HASH-NEXT:   Shift Count: 26
+# SHARED-HASH-NEXT:   Bloom Filter: [0x21000000000000]
+# SHARED-HASH-NEXT:   Buckets: [2]
+# SHARED-HASH-NEXT:   Values: [0xD453A5B1]
+# SHARED-HASH-NEXT: }
+
+# SYSV-ONLY:      .dynsym DYNSYM {{.*}} 000048 18 A {{[0-9]+}} 2 8
+# SYSV-ONLY:      Symbol table '.dynsym' contains 3 entries:
+# SYSV-ONLY:      1: 0000000000004000     0 SECTION LOCAL  DEFAULT {{[0-9]+}} .data
+# SYSV-ONLY-NEXT: 2: {{[0-9a-f]+}}     0 FUNC    GLOBAL DEFAULT {{[0-9]+}} exported
+
+# STATIC-PIE:      Symbol table '.dynsym' contains 2 entries:
+# STATIC-PIE-NEXT:    Num:    Value          Size Type    Bind   Vis      Ndx Name
+# STATIC-PIE-NEXT:      0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND
+# STATIC-PIE-NEXT:      1: 0000000000004000     0 SECTION LOCAL  DEFAULT {{[0-9]+}} .data
+# STATIC-PIE-NOT: exported
+
+# STATIC-PIE-HASH:      HashTable {
+# STATIC-PIE-HASH-NEXT:   Num Buckets: 2
+# STATIC-PIE-HASH-NEXT:   Num Chains: 2
+# STATIC-PIE-HASH-NEXT:   Buckets: [0, 0]
+# STATIC-PIE-HASH-NEXT:   Chains: [0, 0]
+# STATIC-PIE-HASH-NEXT: }
+# STATIC-PIE-HASH:      GnuHashTable {
+# STATIC-PIE-HASH-NEXT:   Num Buckets: 1
+# STATIC-PIE-HASH-NEXT:   First Hashed Symbol Index: 2
+# STATIC-PIE-HASH-NEXT:   Num Mask Words: 1
+# STATIC-PIE-HASH-NEXT:   Shift Count: 26
+# STATIC-PIE-HASH-NEXT:   Bloom Filter: [0x0]
+# STATIC-PIE-HASH-NEXT:   Buckets: [0]
+# STATIC-PIE-HASH-NEXT:   Values: []
+# STATIC-PIE-HASH-NEXT: }
+
+.text
+.globl _start
+.hidden _start
+.type _start, @function
+_start:
+  retl
+  nop
+
+.globl exported
+.type exported, @function
+exported:
+  retl
+  nop
+
+.data
+local:
+  .xword 0
+
+.p2align 1
+aligned16:
+  .half 0
+  .reloc aligned16, R_SPARC_UA16, local+1
+
+.byte 0
+unaligned16:
+  .half 0
+  .reloc unaligned16, R_SPARC_16, local+2
+
+.p2align 2
+aligned32:
+  .word 0
+  .reloc aligned32, R_SPARC_UA32, local+3
+
+.byte 0
+unaligned32:
+  .word 0
+  .reloc unaligned32, R_SPARC_32, local+4
+
+.p2align 3
+aligned64:
+  .xword 0
+  .reloc aligned64, R_SPARC_UA64, local+5
+
+.byte 0
+unaligned64:
+  .xword 0
+  .reloc unaligned64, R_SPARC_64, local+6
diff --git a/lld/test/ELF/sparcv9-ua-dynrel.s b/lld/test/ELF/sparcv9-ua-dynrel.s
index 4f58f95852312..395105291f228 100644
--- a/lld/test/ELF/sparcv9-ua-dynrel.s
+++ b/lld/test/ELF/sparcv9-ua-dynrel.s
@@ -3,9 +3,6 @@
 # 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_16 {{.*}} external
 # CHECK:      R_SPARC_32 {{.*}} external
@@ -14,10 +11,6 @@
 # CHECK:      R_SPARC_UA32 {{.*}} external
 # CHECK:      R_SPARC_UA64 {{.*}} external
 
-# ERR-DAG: error: relocation R_SPARC_16 at offset {{[0-9]+}} against non-preemptible symbol local cannot be converted to R_SPARC_RELATIVE
-# ERR-DAG: error: relocation R_SPARC_32 at offset {{[0-9]+}} against non-preemptible symbol local cannot be converted to R_SPARC_RELATIVE
-# ERR-DAG: error: relocation R_SPARC_UA64 at offset {{[0-9]+}} against non-preemptible symbol local cannot be converted to R_SPARC_RELATIVE
-
 .data
 .p2align 3
 aligned_local64:
@@ -57,24 +50,6 @@ unaligned_external64:
   .xword 0
   .reloc unaligned_external64, R_SPARC_64, external
 
-.ifdef ERR
-.p2align 1
-aligned_local16:
-  .half 0
-  .reloc aligned_local16, R_SPARC_UA16, local
-
-.p2align 2
-aligned_local32:
-  .word 0
-  .reloc aligned_local32, R_SPARC_UA32, local
-
-.p2align 3
-  .byte 0
-unaligned_local64:
-  .xword 0
-  .reloc unaligned_local64, R_SPARC_64, local
-.endif
-
 .hidden local
 local:
   .xword 0



More information about the llvm-commits mailing list