Index | Thread | Search

From:
Kirill A. Korinsky <kirill@korins.ky>
Subject:
OpenBSD/spac64 by LLVM toolchain
To:
OpenBSD tech <tech@openbsd.org>
Date:
Sun, 05 Jul 2026 11:56:06 +0200

Download raw body.

Thread
  • Kirill A. Korinsky:

    OpenBSD/spac64 by LLVM toolchain

tech@,

Here a diff which allows me to build OpenBSD/spac64 by llvm toolchain.

My test cycle from clean snapshot was:

1. Rebuild clang / lld

cd /usr/src/gnu/usr.bin/clang
doas make clean
doas -u build make CC=clang CXX=clang++ -j8
doas make install

2. Rebuild clang / lld as system compiler

cd /usr/src/share/mk
doas make install
cd /usr/src/gnu/usr.bin/clang
doas make clean
doas -u build make CC=clang CXX=clang++ LD=ld.lld -j8
doas make install

3. Rebuild the system itself

cd /usr/src
doas make build -j8
doas make install

4. Rebuild kernel

cd /usr/src/sys/arch/sparc64/compile/GENERIC.MP
doas make clean
doas -u build make config
doas -u make
doas make install

It boots and seems to works, but I haven't tried to build ports.

The medium size diff which is based on
https://github.com/llvm/llvm-project/pull/137919

Index: gnu/llvm/lld/ELF/Driver.cpp
===================================================================
RCS file: /cvs/src/gnu/llvm/lld/ELF/Driver.cpp,v
diff -u -p -r1.24 Driver.cpp
--- gnu/llvm/lld/ELF/Driver.cpp	29 May 2026 11:06:20 -0000	1.24
+++ gnu/llvm/lld/ELF/Driver.cpp	1 Jul 2026 12:05:26 -0000
@@ -1316,9 +1316,10 @@ static SmallVector<StringRef, 0> getSymb
 
 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());
Index: gnu/llvm/lld/ELF/InputSection.cpp
===================================================================
RCS file: /cvs/src/gnu/llvm/lld/ELF/InputSection.cpp,v
diff -u -p -r1.8 InputSection.cpp
--- gnu/llvm/lld/ELF/InputSection.cpp	29 May 2026 11:06:20 -0000	1.8
+++ gnu/llvm/lld/ELF/InputSection.cpp	1 Jul 2026 12:05:26 -0000
@@ -838,6 +838,7 @@ uint64_t InputSectionBase::getRelocTarge
     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();
Index: gnu/llvm/lld/ELF/Relocations.cpp
===================================================================
RCS file: /cvs/src/gnu/llvm/lld/ELF/Relocations.cpp,v
diff -u -p -r1.11 Relocations.cpp
--- gnu/llvm/lld/ELF/Relocations.cpp	29 May 2026 11:06:20 -0000	1.11
+++ gnu/llvm/lld/ELF/Relocations.cpp	1 Jul 2026 12:05:26 -0000
@@ -770,14 +770,18 @@ static void addRelativeReloc(Ctx &ctx, I
 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, Re
   // 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(
 
   // 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
   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
   return needTlsSymbol;
 }
 
-void elf::hexagonTLSSymbolUpdate(Ctx &ctx) {
+void elf::tlsSymbolUpdate(Ctx &ctx) {
   Symbol *sym = ctx.symtab->find("__tls_get_addr");
   if (!sym)
     return;
Index: gnu/llvm/lld/ELF/Relocations.h
===================================================================
RCS file: /cvs/src/gnu/llvm/lld/ELF/Relocations.h,v
diff -u -p -r1.1.1.6 Relocations.h
--- gnu/llvm/lld/ELF/Relocations.h	29 May 2026 11:05:55 -0000	1.1.1.6
+++ gnu/llvm/lld/ELF/Relocations.h	1 Jul 2026 12:05:26 -0000
@@ -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 &, Undefin
 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);
 
Index: gnu/llvm/lld/ELF/Symbols.cpp
===================================================================
RCS file: /cvs/src/gnu/llvm/lld/ELF/Symbols.cpp,v
diff -u -p -r1.7 Symbols.cpp
--- gnu/llvm/lld/ELF/Symbols.cpp	29 May 2026 11:06:20 -0000	1.7
+++ gnu/llvm/lld/ELF/Symbols.cpp	1 Jul 2026 12:05:26 -0000
@@ -173,6 +173,12 @@ uint64_t Symbol::getGotPltOffset(Ctx &ct
          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
Index: gnu/llvm/lld/ELF/Symbols.h
===================================================================
RCS file: /cvs/src/gnu/llvm/lld/ELF/Symbols.h,v
diff -u -p -r1.8 Symbols.h
--- gnu/llvm/lld/ELF/Symbols.h	29 May 2026 11:06:20 -0000	1.8
+++ gnu/llvm/lld/ELF/Symbols.h	1 Jul 2026 12:05:26 -0000
@@ -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;
Index: gnu/llvm/lld/ELF/SyntheticSections.cpp
===================================================================
RCS file: /cvs/src/gnu/llvm/lld/ELF/SyntheticSections.cpp,v
diff -u -p -r1.10 SyntheticSections.cpp
--- gnu/llvm/lld/ELF/SyntheticSections.cpp	29 May 2026 11:06:20 -0000	1.10
+++ gnu/llvm/lld/ELF/SyntheticSections.cpp	1 Jul 2026 12:05:26 -0000
@@ -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::finalizeCont
   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::createSy
   // _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.
Index: gnu/llvm/lld/ELF/Target.cpp
===================================================================
RCS file: /cvs/src/gnu/llvm/lld/ELF/Target.cpp,v
diff -u -p -r1.1.1.6 Target.cpp
--- gnu/llvm/lld/ELF/Target.cpp	29 May 2026 11:05:55 -0000	1.1.1.6
+++ gnu/llvm/lld/ELF/Target.cpp	1 Jul 2026 12:05:26 -0000
@@ -158,6 +158,12 @@ RelExpr TargetInfo::adjustGotPcExpr(RelT
   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;
Index: gnu/llvm/lld/ELF/Target.h
===================================================================
RCS file: /cvs/src/gnu/llvm/lld/ELF/Target.h,v
diff -u -p -r1.1.1.6 Target.h
--- gnu/llvm/lld/ELF/Target.h	29 May 2026 11:05:55 -0000	1.1.1.6
+++ gnu/llvm/lld/ELF/Target.h	1 Jul 2026 12:05:26 -0000
@@ -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.
Index: gnu/llvm/lld/ELF/Writer.cpp
===================================================================
RCS file: /cvs/src/gnu/llvm/lld/ELF/Writer.cpp,v
diff -u -p -r1.10 Writer.cpp
--- gnu/llvm/lld/ELF/Writer.cpp	29 May 2026 11:06:20 -0000	1.10
+++ gnu/llvm/lld/ELF/Writer.cpp	1 Jul 2026 12:05:26 -0000
@@ -625,7 +625,7 @@ static bool isRelroSection(Ctx &ctx, con
   // 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>:
   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>:
   };
   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>:
       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,10 @@ static bool needsPtLoad(OutputSection *s
 static uint64_t computeFlags(Ctx &ctx, uint64_t flags) {
   if (ctx.arg.omagic)
     return PF_R | PF_W | PF_X;
+#ifdef __OpenBSD__
+  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;
Index: gnu/llvm/lld/ELF/Arch/SPARCV9.cpp
===================================================================
RCS file: /cvs/src/gnu/llvm/lld/ELF/Arch/SPARCV9.cpp,v
diff -u -p -r1.1.1.5 SPARCV9.cpp
--- gnu/llvm/lld/ELF/Arch/SPARCV9.cpp	29 May 2026 11:05:55 -0000	1.1.1.5
+++ gnu/llvm/lld/ELF/Arch/SPARCV9.cpp	1 Jul 2026 12:05:26 -0000
@@ -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(
   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(
 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
   }
 }
 
+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,19 +287,89 @@ void SPARCV9::relocate(uint8_t *loc, con
     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*/,
Index: share/mk/bsd.own.mk
===================================================================
RCS file: /cvs/src/share/mk/bsd.own.mk,v
diff -u -p -r1.216 bsd.own.mk
--- share/mk/bsd.own.mk	17 Nov 2025 16:06:09 -0000	1.216
+++ share/mk/bsd.own.mk	1 Jul 2026 12:05:26 -0000
@@ -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
Index: sys/arch/sparc64/conf/Makefile.sparc64
===================================================================
RCS file: /cvs/src/sys/arch/sparc64/conf/Makefile.sparc64,v
diff -u -p -r1.113 Makefile.sparc64
--- sys/arch/sparc64/conf/Makefile.sparc64	5 May 2025 20:43:32 -0000	1.113
+++ sys/arch/sparc64/conf/Makefile.sparc64	1 Jul 2026 12:05:26 -0000
@@ -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
Index: sys/arch/sparc64/stand/bootblk/genassym.sh
===================================================================
RCS file: /cvs/src/sys/arch/sparc64/stand/bootblk/genassym.sh,v
diff -u -p -r1.4 genassym.sh
--- sys/arch/sparc64/stand/bootblk/genassym.sh	2 Apr 2020 06:06:22 -0000	1.4
+++ sys/arch/sparc64/stand/bootblk/genassym.sh	1 Jul 2026 12:05:26 -0000
@@ -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