Index | Thread | Search

From:
Kirill A. Korinsky <kirill@korins.ky>
Subject:
Re: OpenBSD/spac64 by LLVM toolchain
To:
tech@openbsd.org
Date:
Thu, 09 Jul 2026 13:46:17 +0200

Download raw body.

Thread
Here updated version of diff which should match that tb@ is testing on his
sparc64.

Also, I have excluded fixes for ld.so which I send early here:
https://marc.info/?l=openbsd-tech&m=178359162807385&w=2

diff --git gnu/llvm/lld/ELF/Arch/SPARCV9.cpp gnu/llvm/lld/ELF/Arch/SPARCV9.cpp
index 86668c50d3c..7b46ec7db59 100644
--- gnu/llvm/lld/ELF/Arch/SPARCV9.cpp
+++ gnu/llvm/lld/ELF/Arch/SPARCV9.cpp
@@ -6,6 +6,7 @@
 //
 //===----------------------------------------------------------------------===//
 
+#include "OutputSections.h"
 #include "Symbols.h"
 #include "SyntheticSections.h"
 #include "Target.h"
@@ -23,10 +24,17 @@ public:
   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;
   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
 
@@ -35,9 +43,16 @@ SPARCV9::SPARCV9(Ctx &ctx) : TargetInfo(ctx) {
   gotRel = R_SPARC_GLOB_DAT;
   pltRel = R_SPARC_JMP_SLOT;
   relativeRel = R_SPARC_RELATIVE;
+  iRelativeRel = R_SPARC_IRELATIVE;
   symbolicRel = R_SPARC_64;
+  tlsGotRel = R_SPARC_TLS_TPOFF64;
+  tlsModuleIndexRel = R_SPARC_TLS_DTPMOD64;
+  tlsOffsetRel = R_SPARC_TLS_DTPOFF64;
+
+  gotHeaderEntriesNum = 1;
   pltEntrySize = 32;
   pltHeaderSize = 4 * pltEntrySize;
+  usesGotPlt = false;
 
   defaultCommonPageSize = 8192;
   defaultMaxPageSize = 0x100000;
@@ -47,35 +62,74 @@ SPARCV9::SPARCV9(Ctx &ctx) : TargetInfo(ctx) {
 RelExpr SPARCV9::getRelExpr(RelType type, const Symbol &s,
                             const uint8_t *loc) const {
   switch (type) {
+  case R_SPARC_NONE:
+    return R_NONE;
+  case R_SPARC_8:
+  case R_SPARC_16:
   case R_SPARC_32:
+  case R_SPARC_HI22:
+  case R_SPARC_13:
+  case R_SPARC_LO10:
   case R_SPARC_UA32:
   case R_SPARC_64:
-  case R_SPARC_UA64:
-  case R_SPARC_H44:
-  case R_SPARC_M44:
-  case R_SPARC_L44:
   case R_SPARC_HH22:
   case R_SPARC_HM10:
   case R_SPARC_LM22:
-  case R_SPARC_HI22:
-  case R_SPARC_LO10:
+  case R_SPARC_HIX22:
+  case R_SPARC_LOX10:
+  case R_SPARC_H44:
+  case R_SPARC_M44:
+  case R_SPARC_L44:
+  case R_SPARC_UA64:
+  case R_SPARC_UA16:
     return R_ABS;
-  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_PC10:
+  case R_SPARC_PC22:
+  case R_SPARC_WDISP16:
+  case R_SPARC_WDISP19:
+  case R_SPARC_DISP64:
     return R_PC;
   case R_SPARC_GOT10:
-    return R_GOT_OFF;
+  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:
     return R_GOT_OFF;
   case R_SPARC_WPLT30:
+  case R_SPARC_TLS_GD_CALL:
+  case R_SPARC_TLS_LDM_CALL:
     return R_PLT_PC;
-  case R_SPARC_NONE:
-    return R_NONE;
+  case R_SPARC_TLS_GD_HI22:
+  case R_SPARC_TLS_GD_LO10:
+    return R_TLSGD_GOT;
+  case R_SPARC_TLS_GD_ADD:
+  case R_SPARC_TLS_LDM_ADD:
+  case R_SPARC_TLS_LDO_ADD:
+  case R_SPARC_TLS_IE_LD:
+  case R_SPARC_TLS_IE_LDX:
+  case R_SPARC_TLS_IE_ADD:
+    return R_NONE; // TODO: Relax TLS relocations.
+  case R_SPARC_TLS_LDM_HI22:
+  case R_SPARC_TLS_LDM_LO10:
+    return R_TLSLD_GOT;
+  case R_SPARC_TLS_LDO_HIX22:
+  case R_SPARC_TLS_LDO_LOX10:
+    return R_DTPREL;
+  case R_SPARC_TLS_IE_HI22:
+  case R_SPARC_TLS_IE_LO10:
+    return R_GOT;
   case R_SPARC_TLS_LE_HIX22:
   case R_SPARC_TLS_LE_LOX10:
     return R_TPREL;
+  case R_SPARC_GOTDATA_HIX22:
+  case R_SPARC_GOTDATA_LOX10:
+    return R_GOTREL;
   default:
     Err(ctx) << getErrorLoc(ctx, loc) << "unknown relocation (" << type.v
              << ") against symbol " << &s;
@@ -83,73 +137,148 @@ 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;
+}
+
 void SPARCV9::relocate(uint8_t *loc, const Relocation &rel,
                        uint64_t val) const {
+  switch (rel.expr) {
+  case R_RELAX_GOT_OFF:
+    return relaxGot(loc, rel, val);
+  default:
+    break;
+  }
+
   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
     checkUInt(ctx, loc, val, 32, rel);
     write32be(loc, val);
     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_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:
+  case R_SPARC_TLS_GD_CALL:
+  case R_SPARC_TLS_LDM_CALL:
     // 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_22:
-    // V-imm22
-    checkUInt(ctx, loc, val, 22, rel);
-    write32be(loc, (read32be(loc) & ~0x003fffff) | (val & 0x003fffff));
+  case R_SPARC_WDISP22:
+    // V-disp22
+    checkIntUInt(ctx, loc, val, 24, rel);
+    write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 2) & 0x003fffff));
     break;
-  case R_SPARC_GOT22:
-  case R_SPARC_PC22:
-  case R_SPARC_LM22:
-    // T-imm22
+  case R_SPARC_HI22: // Only T-imm22 on 32-bit, despite binutils behavior.
+    // V-imm22
+    checkUInt(ctx, loc, val, 32, rel);
     write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 10) & 0x003fffff));
     break;
-  case R_SPARC_HI22:
+  case R_SPARC_22:
     // V-imm22
-    checkUInt(ctx, loc, val >> 10, 22, rel);
-    write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 10) & 0x003fffff));
+    checkUInt(ctx, loc, val, 22, rel);
+    write32be(loc, (read32be(loc) & ~0x003fffff) | (val & 0x003fffff));
     break;
-  case R_SPARC_WDISP19:
-    // V-disp19
-    checkInt(ctx, loc, val, 21, rel);
-    write32be(loc, (read32be(loc) & ~0x0007ffff) | ((val >> 2) & 0x0007ffff));
+  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_LO10:
   case R_SPARC_GOT10:
   case R_SPARC_PC10:
-    // T-simm10
+  case R_SPARC_TLS_GD_LO10:
+  case R_SPARC_TLS_LDM_LO10:
+  case R_SPARC_TLS_IE_LO10:
+    // T-simm13
     write32be(loc, (read32be(loc) & ~0x000003ff) | (val & 0x000003ff));
     break;
-  case R_SPARC_LO10:
+  case R_SPARC_TLS_LDO_LOX10:
     // T-simm13
     write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff));
     break;
+  case R_SPARC_GOT22:
+  case R_SPARC_LM22:
+  case R_SPARC_TLS_GD_HI22:
+  case R_SPARC_TLS_LDM_HI22:
+  case R_SPARC_TLS_LDO_HIX22: // Not V-simm22, despite binutils behavior.
+  case R_SPARC_TLS_IE_HI22:
+    // T-(s)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_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_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_WDISP19:
+    // V-disp19
+    checkIntUInt(ctx, loc, val, 21, rel);
+    write32be(loc, (read32be(loc) & ~0x0007ffff) | ((val >> 2) & 0x0007ffff));
+    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_LOX10:
+  case R_SPARC_TLS_LE_LOX10:
+    // T-simm13
+    write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) | 0x1c00);
     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:
@@ -158,21 +287,91 @@ 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:
+  case R_SPARC_TLS_GD_ADD:
+  case R_SPARC_TLS_LDM_ADD:
+  case R_SPARC_TLS_LDO_ADD:
+  case R_SPARC_TLS_IE_LD:
+  case R_SPARC_TLS_IE_LDX:
+  case R_SPARC_TLS_IE_ADD:
+    // None
+    break;
+  case R_SPARC_TLS_LE_HIX22: // Not V-imm2, despite binutils behavior.
     // T-imm22
     write32be(loc, (read32be(loc) & ~0x003fffff) | ((~val >> 10) & 0x003fffff));
     break;
-  case R_SPARC_TLS_LE_LOX10:
-    // T-simm13
-    write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) | 0x1C00);
+  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: // Not V-imm22, despite binutils behavior.
+                                 // Non-relaxed case.
+    // T-imm22
+    write32be(loc, (read32be(loc) & ~0x003fffff) |
+                       ((((int64_t)val < 0 ? ~val : val) >> 10) & 0x003fffff));
+    break;
+  case R_SPARC_GOTDATA_LOX10:
+  case R_SPARC_GOTDATA_OP_LOX10: // Non-relaxed case.
+    // T-imm13
+    write32be(loc, (read32be(loc) & ~0x00001fff) | (val & 0x000003ff) |
+                       ((int64_t)val < 0 ? 0x1c00 : 0));
+    break;
+  case R_SPARC_GOTDATA_OP: // Non-relaxed case.
+    // word32
+    // Nothing needs to be done in the non-relaxed case.
+    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_RELAX_GOT_OFF;
+
+    [[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: // Not V-imm22, despite binutils behavior.
+    // 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:
+    // word32
+    // ldx [%rs1 + %rs2], %rd -> add %rs1, %rs2, %rd
+    write32be(loc, (read32be(loc) & 0x3e07c01f) | 0x80000000);
     break;
   default:
     llvm_unreachable("unknown relocation");
   }
 }
 
+void SPARCV9::writeGotHeader(uint8_t *buf) const {
+  // _GLOBAL_OFFSET_TABLE_[0] = _DYNAMIC when a dynamic section exists.
+  if (ctx.mainPart->dynamic)
+    write32(ctx, buf, ctx.mainPart->dynamic->getVA());
+}
+
 void SPARCV9::writePlt(uint8_t *buf, const Symbol & /*sym*/,
                        uint64_t pltEntryAddr) const {
   const uint8_t pltData[] = {
diff --git gnu/llvm/lld/ELF/Driver.cpp gnu/llvm/lld/ELF/Driver.cpp
index de99ea195cf..c61cf1808af 100644
--- gnu/llvm/lld/ELF/Driver.cpp
+++ gnu/llvm/lld/ELF/Driver.cpp
@@ -1316,9 +1316,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 gnu/llvm/lld/ELF/InputSection.cpp gnu/llvm/lld/ELF/InputSection.cpp
index 7402c8454c0..e49c6b2230d 100644
--- gnu/llvm/lld/ELF/InputSection.cpp
+++ gnu/llvm/lld/ELF/InputSection.cpp
@@ -838,6 +838,7 @@ uint64_t InputSectionBase::getRelocTargetVA(Ctx &ctx, const Relocation &r,
     return ctx.in.gotPlt->getVA() + a - p;
   case R_GOTREL:
   case RE_PPC64_RELAX_TOC:
+  case R_RELAX_GOT_OFF:
     return r.sym->getVA(ctx, a) - ctx.in.got->getVA();
   case R_GOTPLTREL:
     return r.sym->getVA(ctx, a) - ctx.in.gotPlt->getVA();
diff --git gnu/llvm/lld/ELF/Relocations.cpp gnu/llvm/lld/ELF/Relocations.cpp
index 863909a9d46..4f311e56db0 100644
--- gnu/llvm/lld/ELF/Relocations.cpp
+++ gnu/llvm/lld/ELF/Relocations.cpp
@@ -770,14 +770,18 @@ static void addRelativeReloc(Ctx &ctx, InputSectionBase &isec,
 template <class PltSection, class GotPltSection>
 static void addPltEntry(Ctx &ctx, PltSection &plt, GotPltSection &gotPlt,
                         RelocationBaseSection &rel, RelType type, Symbol &sym) {
+  RelExpr expr = sym.isPreemptible ? R_ADDEND : R_ABS;
   plt.addEntry(sym);
-  gotPlt.addEntry(sym);
-  if (sym.isPreemptible)
-    rel.addReloc(
-        {type, &gotPlt, sym.getGotPltOffset(ctx), true, sym, 0, R_ADDEND});
-  else
+  if (ctx.target->usesGotPlt) {
+    gotPlt.addEntry(sym);
+    // The relocation is applied to the .got.plt entry.
+    rel.addReloc({type, &gotPlt, sym.getGotPltOffset(ctx), !!sym.isPreemptible,
+                  sym, 0, expr});
+  } else {
+    // The relocation is applied to the .plt entry.
     rel.addReloc(
-        {type, &gotPlt, sym.getGotPltOffset(ctx), false, sym, 0, R_ABS});
+        {type, &plt, sym.getPltOffset(ctx), !!sym.isPreemptible, sym, 0, expr});
+  }
 }
 
 void elf::addGotEntry(Ctx &ctx, Symbol &sym) {
@@ -945,25 +949,32 @@ void RelocScan::process(RelExpr expr, RelType type, uint64_t offset,
   // indirection.
   const bool isIfunc = sym.isGnuIFunc();
   if (!sym.isPreemptible && (!isIfunc || ctx.arg.zIfuncNoplt)) {
-    if (expr != R_GOT_PC) {
+    if (expr != R_GOT_PC && expr != R_GOT_OFF) {
       // The 0x8000 bit of r_addend of R_PPC_PLTREL24 is used to choose call
       // stub type. It should be ignored if optimized to R_PC.
       if (ctx.arg.emachine == EM_PPC && expr == RE_PPC32_PLTREL)
         addend &= ~0x8000;
       // R_HEX_GD_PLT_B22_PCREL (call a@GDPLT) is transformed into
       // call __tls_get_addr even if the symbol is non-preemptible.
+      // Same deal for R_SPARC_TLS_LDM_CALL (call x@TLSPLT).
       if (!(ctx.arg.emachine == EM_HEXAGON &&
             (type == R_HEX_GD_PLT_B22_PCREL ||
              type == R_HEX_GD_PLT_B22_PCREL_X ||
-             type == R_HEX_GD_PLT_B32_PCREL_X)))
+             type == R_HEX_GD_PLT_B32_PCREL_X)) &&
+          !(ctx.arg.emachine == EM_SPARCV9 && type == R_SPARC_TLS_LDM_CALL))
         expr = fromPlt(expr);
     } else if (!isAbsoluteValue(sym) ||
                (type == R_PPC64_PCREL_OPT && ctx.arg.emachine == EM_PPC64)) {
-      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
+      if (expr == R_GOT_PC)
+        expr = ctx.target->adjustGotPcExpr(type, addend,
+                                           sec->content().data() + offset);
+      else if (expr == R_GOT_OFF)
+        expr = ctx.target->adjustGotOffExpr(type, sym, addend,
+                                            sec->content().data() + offset);
+
+      // If the target adjusted the expression to R_RELAX_GOT_*, we may end up
       // needing the GOT if we can't relax everything.
-      if (expr == R_RELAX_GOT_PC)
+      if (expr == R_RELAX_GOT_PC || expr == R_RELAX_GOT_OFF)
         ctx.in.got->hasGotOffRel.store(true, std::memory_order_relaxed);
     }
   }
@@ -1236,12 +1247,13 @@ unsigned RelocScan::handleTlsRelocation(RelExpr expr, RelType type,
 
   // ARM, Hexagon, LoongArch and RISC-V do not support GD/LD to IE/LE
   // optimizations.
+  // SPARC support for GD/LD to IE/LE optimizations is not yet implemented.
   // RISC-V supports TLSDESC to IE/LE optimizations.
   // For PPC64, if the file has missing R_PPC64_TLSGD/R_PPC64_TLSLD, disable
   // optimization as well.
   bool execOptimize =
       !ctx.arg.shared && ctx.arg.emachine != EM_ARM &&
-      ctx.arg.emachine != EM_HEXAGON &&
+      ctx.arg.emachine != EM_HEXAGON && ctx.arg.emachine != EM_SPARCV9 &&
       (ctx.arg.emachine != EM_LOONGARCH || execOptimizeInLoongArch) &&
       !(isRISCV && expr != R_TLSDESC_PC && expr != R_TLSDESC_CALL) &&
       !sec->file->ppc64DisableTLSRelax;
@@ -2233,11 +2245,11 @@ bool ThunkCreator::createThunks(uint32_t pass,
   return addressesChanged;
 }
 
-// The following aid in the conversion of call x@GDPLT to call __tls_get_addr
-// hexagonNeedsTLSSymbol scans for relocations would require a call to
-// __tls_get_addr.
-// hexagonTLSSymbolUpdate rebinds the relocation to __tls_get_addr.
-bool elf::hexagonNeedsTLSSymbol(ArrayRef<OutputSection *> outputSections) {
+// The following aid in the conversion of call x@GDPLT (Hexagon) and
+// call x@TLSPLT (SPARC) to call __tls_get_addr. needsTLSSymbol scans
+// for relocations that would require a call to __tls_get_addr.
+// tlsSymbolUpdate rebinds the relocation to __tls_get_addr.
+bool elf::needsTLSSymbol(ArrayRef<OutputSection *> outputSections) {
   bool needTlsSymbol = false;
   forEachInputSectionDescription(
       outputSections, [&](OutputSection *os, InputSectionDescription *isd) {
@@ -2251,7 +2263,7 @@ bool elf::hexagonNeedsTLSSymbol(ArrayRef<OutputSection *> outputSections) {
   return needTlsSymbol;
 }
 
-void elf::hexagonTLSSymbolUpdate(Ctx &ctx) {
+void elf::tlsSymbolUpdate(Ctx &ctx) {
   Symbol *sym = ctx.symtab->find("__tls_get_addr");
   if (!sym)
     return;
diff --git gnu/llvm/lld/ELF/Relocations.h gnu/llvm/lld/ELF/Relocations.h
index 05c35d03e07..c34a57f6c2e 100644
--- gnu/llvm/lld/ELF/Relocations.h
+++ gnu/llvm/lld/ELF/Relocations.h
@@ -61,6 +61,7 @@ enum RelExpr {
   R_PLT_GOTPLT,
   R_PLT_GOTREL,
   R_RELAX_HINT,
+  R_RELAX_GOT_OFF,
   R_RELAX_GOT_PC,
   R_RELAX_GOT_PC_NOPIC,
   R_RELAX_TLS_GD_TO_IE,
@@ -171,8 +172,8 @@ bool maybeReportUndefined(Ctx &, Undefined &sym, InputSectionBase &sec,
 void postScanRelocations(Ctx &ctx);
 void addGotEntry(Ctx &ctx, Symbol &sym);
 
-void hexagonTLSSymbolUpdate(Ctx &ctx);
-bool hexagonNeedsTLSSymbol(ArrayRef<OutputSection *> outputSections);
+void tlsSymbolUpdate(Ctx &ctx);
+bool needsTLSSymbol(ArrayRef<OutputSection *> outputSections);
 
 bool isAbsolute(const Symbol &sym);
 
diff --git gnu/llvm/lld/ELF/Symbols.cpp gnu/llvm/lld/ELF/Symbols.cpp
index 6f74421582c..71fa8a61a96 100644
--- gnu/llvm/lld/ELF/Symbols.cpp
+++ gnu/llvm/lld/ELF/Symbols.cpp
@@ -173,6 +173,12 @@ uint64_t Symbol::getGotPltOffset(Ctx &ctx) const {
          ctx.target->gotEntrySize;
 }
 
+uint64_t Symbol::getPltOffset(Ctx &ctx) const {
+  if (isInIplt)
+    return getPltIdx(ctx) * ctx.target->ipltEntrySize;
+  return ctx.in.plt->headerSize + getPltIdx(ctx) * ctx.target->pltEntrySize;
+}
+
 uint64_t Symbol::getPltVA(Ctx &ctx) const {
   uint64_t outVA = isInIplt ? ctx.in.iplt->getVA() +
                                   getPltIdx(ctx) * ctx.target->ipltEntrySize
diff --git gnu/llvm/lld/ELF/Symbols.h gnu/llvm/lld/ELF/Symbols.h
index bb9e304151d..1fbb8f1cd82 100644
--- gnu/llvm/lld/ELF/Symbols.h
+++ gnu/llvm/lld/ELF/Symbols.h
@@ -207,6 +207,7 @@ public:
   uint64_t getGotVA(Ctx &) const;
   uint64_t getGotPltOffset(Ctx &) const;
   uint64_t getGotPltVA(Ctx &) const;
+  uint64_t getPltOffset(Ctx &) const;
   uint64_t getPltVA(Ctx &) const;
   uint64_t getSize() const;
   OutputSection *getOutputSection() const;
diff --git gnu/llvm/lld/ELF/SyntheticSections.cpp gnu/llvm/lld/ELF/SyntheticSections.cpp
index 70dc5a53ff4..57fcfc4cf3a 100644
--- gnu/llvm/lld/ELF/SyntheticSections.cpp
+++ gnu/llvm/lld/ELF/SyntheticSections.cpp
@@ -1505,6 +1505,7 @@ DynamicSection<ELFT>::computeContents() {
         addInt(DT_RISCV_VARIANT_CC, 0);
       [[fallthrough]];
     default:
+      assert(ctx.target->usesGotPlt);
       addInSec(DT_PLTGOT, *ctx.in.gotPlt);
       break;
     }
@@ -1720,14 +1721,31 @@ void RelocationBaseSection::finalizeContents() {
   else
     getParent()->link = 0;
 
-  if (ctx.in.relaPlt.get() == this && ctx.in.gotPlt->getParent()) {
-    getParent()->flags |= ELF::SHF_INFO_LINK;
-    getParent()->info = ctx.in.gotPlt->getParent()->sectionIndex;
+  if (ctx.in.relaPlt.get() == this) {
+    if (ctx.target->usesGotPlt && ctx.in.gotPlt->getParent()) {
+      getParent()->flags |= ELF::SHF_INFO_LINK;
+      getParent()->info = ctx.in.gotPlt->getParent()->sectionIndex;
+    } else if (ctx.in.plt->getParent()) {
+      getParent()->flags |= ELF::SHF_INFO_LINK;
+      getParent()->info = ctx.in.plt->getParent()->sectionIndex;
+    }
   }
 }
 
 void DynamicReloc::finalize(Ctx &ctx, SymbolTableBaseSection *symt) {
   r_offset = getOffset();
+  if (ctx.arg.emachine == EM_SPARCV9 && type == R_SPARC_UA64 &&
+      needsDynSymIndex() && !sym->isPreemptible) {
+    if (r_offset % 8 != 0) {
+      Err(ctx) << "R_SPARC_UA64 relocation at offset " << r_offset
+               << " against non-preemptible symbol " << sym
+               << " is not 8-byte aligned";
+    } else {
+      type = ctx.target->relativeRel;
+      isAgainstSymbol = false;
+      expr = R_ABS;
+    }
+  }
   r_sym = getSymIndex(symt);
   addend = computeAddend(ctx);
   isFinal = true; // Catch errors
@@ -2609,8 +2627,10 @@ PltSection::PltSection(Ctx &ctx)
 
   // The PLT needs to be writable on SPARC as the dynamic linker will
   // modify the instructions in the PLT entries.
-  if (ctx.arg.emachine == EM_SPARCV9)
+  if (ctx.arg.emachine == EM_SPARCV9) {
     this->flags |= SHF_WRITE;
+    addralign = 256;
+  }
 }
 
 void PltSection::writeTo(uint8_t *buf) {
@@ -4882,10 +4902,12 @@ template <class ELFT> void elf::createSyntheticSections(Ctx &ctx) {
   // _GLOBAL_OFFSET_TABLE_ is defined relative to either .got.plt or .got. Treat
   // it as a relocation and ensure the referenced section is created.
   if (ctx.sym.globalOffsetTable && ctx.arg.emachine != EM_MIPS) {
-    if (ctx.target->gotBaseSymInGotPlt)
+    if (ctx.target->gotBaseSymInGotPlt) {
+      assert(ctx.target->usesGotPlt);
       ctx.in.gotPlt->hasGotPltOffRel = true;
-    else
+    } else {
       ctx.in.got->hasGotOffRel = true;
+    }
   }
 
   // We always need to add rel[a].plt to output if it has entries.
diff --git gnu/llvm/lld/ELF/Target.cpp gnu/llvm/lld/ELF/Target.cpp
index 3fc3e3f16e9..e8108ea1859 100644
--- gnu/llvm/lld/ELF/Target.cpp
+++ gnu/llvm/lld/ELF/Target.cpp
@@ -158,6 +158,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 gnu/llvm/lld/ELF/Target.h gnu/llvm/lld/ELF/Target.h
index 30fcb02b43f..e867ad7e84c 100644
--- gnu/llvm/lld/ELF/Target.h
+++ gnu/llvm/lld/ELF/Target.h
@@ -135,7 +135,8 @@ public:
 
   uint64_t getImageBase() const;
 
-  // True if _GLOBAL_OFFSET_TABLE_ is relative to .got.plt, false if .got.
+  // True if _GLOBAL_OFFSET_TABLE_ is relative to .got.plt, false if .got. If
+  // true, usesGotPlt must also be true.
   bool gotBaseSymInGotPlt = false;
 
   static constexpr RelType noneRel = 0;
@@ -162,6 +163,8 @@ public:
   // On PPC ELF V2 abi, the first entry in the .got is the .TOC.
   unsigned gotHeaderEntriesNum = 0;
 
+  bool usesGotPlt = true;
+
   // On PPC ELF V2 abi, the dynamic section needs DT_PPC64_OPT (DT_LOPROC + 3)
   // to be set to 0x2 if there can be multiple TOC's. Although we do not emit
   // multiple TOC's, there can be a mix of TOC and NOTOC addressing which
@@ -186,6 +189,8 @@ public:
   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 gnu/llvm/lld/ELF/Writer.cpp gnu/llvm/lld/ELF/Writer.cpp
index 6ab2467857e..5f611fd3d0f 100644
--- gnu/llvm/lld/ELF/Writer.cpp
+++ gnu/llvm/lld/ELF/Writer.cpp
@@ -625,7 +625,7 @@ static bool isRelroSection(Ctx &ctx, const OutputSection *sec) {
   // by default resolved lazily, so we usually cannot put it into RELRO.
   // However, if "-z now" is given, the lazy symbol resolution is
   // disabled, which enables us to put it into RELRO.
-  if (sec == ctx.in.gotPlt->getParent())
+  if (ctx.target->usesGotPlt && sec == ctx.in.gotPlt->getParent())
 #ifndef __OpenBSD__
     return ctx.arg.zNow;
 #else
@@ -863,10 +863,14 @@ template <class ELFT> void Writer<ELFT>::setReservedSymbolSections() {
   if (ctx.sym.globalOffsetTable) {
     // The _GLOBAL_OFFSET_TABLE_ symbol is defined by target convention usually
     // to the start of the .got or .got.plt section.
-    InputSection *sec = ctx.in.gotPlt.get();
-    if (!ctx.target->gotBaseSymInGotPlt)
+    InputSection *sec;
+    if (ctx.target->gotBaseSymInGotPlt) {
+      assert(ctx.target->usesGotPlt);
+      sec = ctx.in.gotPlt.get();
+    } else {
       sec = ctx.in.mipsGot ? cast<InputSection>(ctx.in.mipsGot.get())
                            : cast<InputSection>(ctx.in.got.get());
+    }
     ctx.sym.globalOffsetTable->section = sec;
   }
 
@@ -1544,9 +1548,9 @@ template <class ELFT> void Writer<ELFT>::finalizeAddressDependentContent() {
   };
   finalizeOrderDependentContent();
 
-  // Converts call x@GDPLT to call __tls_get_addr
-  if (ctx.arg.emachine == EM_HEXAGON)
-    hexagonTLSSymbolUpdate(ctx);
+  // Converts call x@GDPLT/x@TLSPLT to call __tls_get_addr.
+  if (ctx.arg.emachine == EM_HEXAGON || ctx.arg.emachine == EM_SPARCV9)
+    tlsSymbolUpdate(ctx);
 
   if (ctx.arg.randomizeSectionPadding)
     randomizeSectionPadding(ctx);
@@ -2042,10 +2046,10 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
       sec->addrExpr = [=] { return i->second; };
   }
 
-  // With the ctx.outputSections available check for GDPLT relocations
+  // With the ctx.outputSections available check for GDPLT/TLSPLT relocations
   // and add __tls_get_addr symbol if needed.
-  if (ctx.arg.emachine == EM_HEXAGON &&
-      hexagonNeedsTLSSymbol(ctx.outputSections)) {
+  if ((ctx.arg.emachine == EM_HEXAGON || ctx.arg.emachine == EM_SPARCV9) &&
+      needsTLSSymbol(ctx.outputSections)) {
     Symbol *sym =
         ctx.symtab->addSymbol(Undefined{ctx.internalFile, "__tls_get_addr",
                                         STB_GLOBAL, STV_DEFAULT, STT_NOTYPE});
@@ -2311,6 +2315,15 @@ static bool needsPtLoad(OutputSection *sec) {
 static uint64_t computeFlags(Ctx &ctx, uint64_t flags) {
   if (ctx.arg.omagic)
     return PF_R | PF_W | PF_X;
+#ifdef __OpenBSD__
+  // The sparc64 static PIE startup code reads an instruction from .text to
+  // locate _DYNAMIC before applying relocations.
+  if (ctx.arg.emachine == EM_SPARCV9 && ctx.arg.pie && !ctx.arg.shared &&
+      ctx.sharedFiles.empty() && (flags & PF_X))
+    return flags | PF_R;
+  if (ctx.arg.emachine == EM_SPARCV9 && (flags & PF_X) && (flags & PF_W))
+    return flags;
+#endif
   if (ctx.arg.executeOnly && (flags & PF_X))
     return flags & ~PF_R;
   return flags;
diff --git share/mk/bsd.own.mk share/mk/bsd.own.mk
index 26028b653be..bc7f12cecb9 100644
--- share/mk/bsd.own.mk
+++ share/mk/bsd.own.mk
@@ -16,8 +16,8 @@ SKEY?=		yes
 YP?=		yes
 
 CLANG_ARCH=aarch64 amd64 arm i386 mips64 mips64el powerpc powerpc64 riscv64 sparc64
-GCC4_ARCH=alpha hppa m88k sh sparc64
-LLD_ARCH=aarch64 amd64 arm i386 powerpc powerpc64 riscv64
+GCC4_ARCH=alpha hppa m88k sh
+LLD_ARCH=aarch64 amd64 arm i386 powerpc powerpc64 riscv64 sparc64
 LLDB_ARCH=aarch64 amd64
 
 # Can't use ${CLANG_ARCH} ${GCC4_ARCH} below because of sparc64
diff --git sys/arch/sparc64/conf/Makefile.sparc64 sys/arch/sparc64/conf/Makefile.sparc64
index 6ef6d052de3..f694a46347e 100644
--- sys/arch/sparc64/conf/Makefile.sparc64
+++ sys/arch/sparc64/conf/Makefile.sparc64
@@ -122,12 +122,16 @@ ioconf.o: ioconf.c
 ld.script: ${_machdir}/conf/ld.script
 	cp ${_machdir}/conf/ld.script $@
 
+gapdummy.o:
+	echo '__asm(".section .rodata,\"a\"");' > gapdummy.c
+	${CC} -c ${CFLAGS} ${CPPFLAGS} gapdummy.c -o $@
+
 makegap.sh:
 	cp $S/conf/makegap.sh $@
 
-MAKE_GAP = LD="${LD}" sh makegap.sh 0x00000000
+MAKE_GAP = LD="${LD}" sh makegap.sh 0x00000000 gapdummy.o
 
-gap.o:	Makefile makegap.sh vers.o
+gap.o:	Makefile makegap.sh gapdummy.o vers.o
 	${MAKE_GAP}
 
 vers.o: ${SYSTEM_DEP:Ngap.o}
@@ -136,7 +140,7 @@ vers.o: ${SYSTEM_DEP:Ngap.o}
 
 clean:
 	rm -f *bsd *bsd.gdb *.[dio] [a-z]*.s assym.* \
-	    gap.link ld.script lorder makegap.sh param.c
+	    gap.link gapdummy.c ld.script lorder makegap.sh param.c
 
 cleandir: clean
 	rm -f Makefile *.h ioconf.c options machine ${_mach} vers.c
diff --git sys/arch/sparc64/stand/bootblk/genassym.sh sys/arch/sparc64/stand/bootblk/genassym.sh
index 6ca7a09ffa0..7490a21d7c2 100644
--- sys/arch/sparc64/stand/bootblk/genassym.sh
+++ sys/arch/sparc64/stand/bootblk/genassym.sh
@@ -164,9 +164,9 @@ $0 ~ /^endif/ {
 		printf("printf(\"#define " $2 " %%ld\\n\", (%s)" value ");\n", type);
 	else if (fcode) {
 		if (doing_member)
-			printf("__asm(\"XYZZY : %s d# %%%s0 + ;\" : : \"%s\" (%s));\n", $2, asmprint, asmtype, value);
+			printf("__asm(\".ascii \\\"XYZZY : %s d# %%%s0 + ;\\\"\" : : \"%s\" (%s));\n", $2, asmprint, asmtype, value);
 		else
-			printf("__asm(\"XYZZY d# %%%s0 constant %s\" : : \"%s\" (%s));\n", asmprint, $2, asmtype, value);
+			printf("__asm(\".ascii \\\"XYZZY d# %%%s0 constant %s\\\"\" : : \"%s\" (%s));\n", asmprint, $2, asmtype, value);
 	} else
 		printf("__asm(\"XYZZY %s %%%s0\" : : \"%s\" (%s));\n", $2, asmprint, asmtype, value);
 	next;
@@ -204,7 +204,7 @@ elif [ "$fcode" = 1 ]; then
 	# Kill all of the "#" and "$" modifiers; locore.s already
 	# prepends the correct "constant" modifier.
 	"$@" -S "${genassym_temp}/assym.c" -o - | sed -e 's/\$//g' | \
-	    sed -n 's/.*XYZZY//gp'
+	    sed -n 's/.*\.ascii[[:space:]]*"XYZZY\([^"]*\)".*/\1/p'
 else
 	# Kill all of the "#" and "$" modifiers; locore.s already
 	# prepends the correct "constant" modifier.


-- 
wbr, Kirill