Index | Thread | Search

From:
Robert Nagy <robert@openbsd.org>
Subject:
Re: LLVM toolchain for sparc64
To:
semarie@online.fr, tech@openbsd.org
Date:
Thu, 30 Jul 2026 14:29:14 +0200

Download raw body.

Thread
fine by me

On 30/07/26 13:38 +0200, Kirill A. Korinsky wrote:
> Folks,
> 
> I'd like to commit to devel/llvm/22 and gnu/llvm my cumulative patch for
> support sparc64 by LLVM toolchain.
> 
> Not OpenBSD related bits was upstreamed as a few PRs and some parts are
> actually already merged.
> 
> More or less this diff was tested by me and tb@ who runs his bulk during
> which many small edge cases was discovered and fixed.
> 
> Here two diffs for the base and ports, I did my best to keep in
> synchronized.
> 
> I haven't backported that changes to LLVM before 22, but we already mainly
> switched to LLVM 22 everywhere.
> 
> I think it may have some missed pieces, but working in the tree will make
> testing it simpler.
> 
> Ok to commit?
> 
> -- 
> wbr, Kirill

> Index: gnu/llvm/lld/ELF/Driver.cpp
> ===================================================================
> RCS file: /home/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	30 Jul 2026 10:58:21 -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/InputFiles.cpp
> ===================================================================
> RCS file: /home/cvs/src/gnu/llvm/lld/ELF/InputFiles.cpp,v
> diff -u -p -r1.7 InputFiles.cpp
> --- gnu/llvm/lld/ELF/InputFiles.cpp	29 May 2026 11:06:20 -0000	1.7
> +++ gnu/llvm/lld/ELF/InputFiles.cpp	30 Jul 2026 10:58:21 -0000
> @@ -1213,6 +1213,19 @@ InputSectionBase *ObjFile<ELFT>::createI
>  
>  // Initialize symbols. symbols is a parallel array to the corresponding ELF
>  // symbol table.
> +//
> +// LLVM's SPARC assembler and OpenBSD ld.so do not implement application
> +// register metadata. Accept GCC scratch declarations, but keep them out of
> +// ordinary symbol resolution.
> +template <class ELFT>
> +static bool isSparcRegister(const Ctx &ctx, const typename ELFT::Sym &sym) {
> +  return ctx.arg.emachine == EM_SPARCV9 && sym.getType() == STT_SPARC_REGISTER;
> +}
> +
> +static bool isSparcScratchRegister(uint64_t value) {
> +  return value == 2 || value == 3 || value == 6 || value == 7;
> +}
> +
>  template <class ELFT>
>  void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
>    ArrayRef<Elf_Sym> eSyms = this->getELFSyms<ELFT>();
> @@ -1221,14 +1234,25 @@ void ObjFile<ELFT>::initializeSymbols(co
>  
>    // Some entries have been filled by LazyObjFile.
>    auto *symtab = ctx.symtab.get();
> -  for (size_t i = firstGlobal, end = eSyms.size(); i != end; ++i)
> -    if (!symbols[i])
> -      symbols[i] = symtab->insert(CHECK2(eSyms[i].getName(stringTable), this));
> +  for (size_t i = firstGlobal, end = eSyms.size(); i != end; ++i) {
> +    const Elf_Sym &eSym = eSyms[i];
> +    if (isSparcRegister<ELFT>(ctx, eSym)) {
> +      StringRef name = CHECK2(eSym.getName(stringTable), this);
> +      if (eSym.getBinding() != STB_GLOBAL || eSym.st_shndx != SHN_UNDEF ||
> +          !name.empty() || !isSparcScratchRegister(eSym.st_value))
> +        Err(ctx) << this << ": unsupported SPARC register declaration";
> +      symbols[i] = ctx.dummySym;
> +    } else if (!symbols[i]) {
> +      symbols[i] = symtab->insert(CHECK2(eSym.getName(stringTable), this));
> +    }
> +  }
>  
>    // Perform symbol resolution on non-local symbols.
>    SmallVector<unsigned, 32> undefineds;
>    for (size_t i = firstGlobal, end = eSyms.size(); i != end; ++i) {
>      const Elf_Sym &eSym = eSyms[i];
> +    if (isSparcRegister<ELFT>(ctx, eSym))
> +      continue;
>      uint32_t secIdx = eSym.st_shndx;
>      if (secIdx == SHN_UNDEF) {
>        undefineds.push_back(i);
> @@ -1330,6 +1354,8 @@ template <class ELFT> void ObjFile<ELFT>
>    ArrayRef<Elf_Sym> eSyms = this->getELFSyms<ELFT>();
>    for (size_t i = firstGlobal, end = eSyms.size(); i != end; ++i) {
>      const Elf_Sym &eSym = eSyms[i];
> +    if (isSparcRegister<ELFT>(ctx, eSym))
> +      continue;
>      Symbol &sym = *symbols[i];
>      uint32_t secIdx = eSym.st_shndx;
>      uint8_t binding = eSym.getBinding();
> @@ -1692,6 +1718,9 @@ template <class ELFT> void SharedFile::p
>    ArrayRef<Elf_Sym> syms = this->getGlobalELFSyms<ELFT>();
>    for (size_t i = 0, e = syms.size(); i != e; ++i) {
>      const Elf_Sym &sym = syms[i];
> +    // A SPARC register declaration is DSO metadata, not a symbol dependency.
> +    if (isSparcRegister<ELFT>(ctx, sym))
> +      continue;
>  
>      // ELF spec requires that all local symbols precede weak or global
>      // symbols in each symbol table, and the index of first non-local symbol
> Index: gnu/llvm/lld/ELF/InputSection.cpp
> ===================================================================
> RCS file: /home/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	30 Jul 2026 10:58:21 -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: /home/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	30 Jul 2026 10:58:21 -0000
> @@ -170,8 +170,9 @@ bool lld::elf::needsGot(RelExpr expr) {
>  static bool isRelExpr(RelExpr expr) {
>    return oneof<R_PC, R_GOTREL, R_GOTPLTREL, RE_ARM_PCA, RE_MIPS_GOTREL,
>                 RE_PPC64_CALL, RE_PPC64_RELAX_TOC, RE_AARCH64_PAGE_PC,
> -               R_RELAX_GOT_PC, RE_RISCV_PC_INDIRECT, RE_PPC64_RELAX_GOT_PC,
> -               RE_LOONGARCH_PAGE_PC, RE_LOONGARCH_PC_INDIRECT>(expr);
> +               R_RELAX_GOT_OFF, R_RELAX_GOT_PC, RE_RISCV_PC_INDIRECT,
> +               RE_PPC64_RELAX_GOT_PC, RE_LOONGARCH_PAGE_PC,
> +               RE_LOONGARCH_PC_INDIRECT>(expr);
>  }
>  
>  static RelExpr toPlt(RelExpr expr) {
> @@ -770,14 +771,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 +950,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);
>      }
>    }
> @@ -1022,8 +1034,14 @@ void RelocScan::process(RelExpr expr, Re
>                      (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 && !sym.isPreemptible)) {
> +    bool useRelative =
> +        rel == ctx.target->symbolicRel && !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);
>        return;
>      }
> @@ -1066,6 +1084,7 @@ void RelocScan::process(RelExpr expr, Re
>            return;
>          }
>        }
> +      ctx.target->prepareDynamicReloc(rel, sym, *sec);
>        part.relaDyn->addSymbolReloc(rel, *sec, offset, sym, addend, type);
>  
>        // MIPS ABI turns using of GOT and dynamic relocations inside out.
> @@ -1236,12 +1255,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;
> @@ -1585,10 +1605,7 @@ void elf::postScanRelocations(Ctx &ctx) 
>        } else {
>          assert(sym.isFunc() && sym.hasFlag(NEEDS_PLT));
>          if (!sym.isDefined()) {
> -          replaceWithDefined(ctx, sym, *ctx.in.plt,
> -                             ctx.target->pltHeaderSize +
> -                                 ctx.target->pltEntrySize * sym.getPltIdx(ctx),
> -                             0);
> +          replaceWithDefined(ctx, sym, *ctx.in.plt, sym.getPltOffset(ctx), 0);
>            sym.setFlags(NEEDS_COPY);
>            if (ctx.arg.emachine == EM_PPC) {
>              // PPC32 canonical PLT entries are at the beginning of .glink
> @@ -2233,11 +2250,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 +2268,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: /home/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	30 Jul 2026 10:58:21 -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: /home/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	30 Jul 2026 10:58:21 -0000
> @@ -173,11 +173,20 @@ 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.target->getPltEntryOffset(getPltIdx(ctx),
> +                                       ctx.in.plt->headerSize);
> +}
> +
>  uint64_t Symbol::getPltVA(Ctx &ctx) const {
>    uint64_t outVA = isInIplt ? ctx.in.iplt->getVA() +
>                                    getPltIdx(ctx) * ctx.target->ipltEntrySize
> -                            : ctx.in.plt->getVA() + ctx.in.plt->headerSize +
> -                                  getPltIdx(ctx) * ctx.target->pltEntrySize;
> +                            : ctx.in.plt->getVA() +
> +                                  ctx.target->getPltEntryOffset(
> +                                      getPltIdx(ctx),
> +                                      ctx.in.plt->headerSize);
>  
>    // While linking microMIPS code PLT code are always microMIPS
>    // code. Set the less-significant bit to track that fact.
> Index: gnu/llvm/lld/ELF/Symbols.h
> ===================================================================
> RCS file: /home/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	30 Jul 2026 10:58:21 -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: /home/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	30 Jul 2026 10:58:21 -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,20 @@ 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();
> +  ctx.target->finalizeDynamicReloc(*this);
>    r_sym = getSymIndex(symt);
>    addend = computeAddend(ctx);
>    isFinal = true; // Catch errors
> @@ -2176,12 +2183,12 @@ void SymbolTableBaseSection::finalizeCon
>      return;
>    }
>  
> -  // If it is a .dynsym, there should be no local symbols, but we need
> -  // to do a few things for the dynamic linker.
> +  // If it is a .dynsym, 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;
> +  // Because the first symbol entry is a null entry, add one to the number of
> +  // local symbols.
> +  getParent()->info = numLocalDynamicSymbols + 1;
>  
>    if (getPartition(ctx).gnuHashTab) {
>      // NB: It also sorts Symbols to meet the GNU hash table requirements.
> @@ -2201,7 +2208,7 @@ void SymbolTableBaseSection::finalizeCon
>  
>  // The ELF spec requires that all local symbols precede global symbols, so we
>  // sort symbol entries in this function. (For .dynsym, we don't do that because
> -// symbols for dynamic linking are inherently all globals.)
> +// local symbols are inserted before global symbols.)
>  //
>  // Aside from above, we put local symbols in groups starting with the STT_FILE
>  // symbol. That is convenient for purpose of identifying where are local symbols
> @@ -2235,6 +2242,13 @@ void SymbolTableBaseSection::addSymbol(S
>    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.mainPart->dynSymTab.get())
>      return sym.dynsymIndex;
> @@ -2508,7 +2522,8 @@ void GnuHashTableSection::addSymbols(Sma
>    // its type correctly.
>    auto mid =
>        std::stable_partition(v.begin(), v.end(), [&](const SymbolTableEntry &s) {
> -        return !s.sym->isDefined() || s.sym->partition != partition;
> +        return s.sym->isLocal() || !s.sym->isDefined() ||
> +               s.sym->partition != partition;
>        });
>  
>    // We chose load factor 4 for the on-disk hash table. For each hash
> @@ -2574,6 +2589,8 @@ void HashTableSection::writeTo(uint8_t *
>  
>    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;
> @@ -2609,20 +2626,22 @@ 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) {
>    // At beginning of PLT, we have code to call the dynamic
>    // linker to resolve dynsyms at runtime. Write such code.
>    ctx.target->writePltHeader(buf);
> -  size_t off = headerSize;
>  
>    for (const Symbol *sym : entries) {
> +    size_t off = sym->getPltOffset(ctx);
>      ctx.target->writePlt(buf + off, *sym, getVA() + off);
> -    off += ctx.target->pltEntrySize;
>    }
> +  ctx.target->finalizePlt(buf);
>  }
>  
>  void PltSection::addEntry(Symbol &sym) {
> @@ -2645,11 +2664,8 @@ bool PltSection::isNeeded() const {
>  void PltSection::addSymbols() {
>    ctx.target->addPltHeaderSymbols(*this);
>  
> -  size_t off = headerSize;
> -  for (size_t i = 0; i < entries.size(); ++i) {
> -    ctx.target->addPltSymbols(*this, off);
> -    off += ctx.target->pltEntrySize;
> -  }
> +  for (const Symbol *sym : entries)
> +    ctx.target->addPltSymbols(*this, sym->getPltOffset(ctx));
>  }
>  
>  IpltSection::IpltSection(Ctx &ctx)
> @@ -4882,10 +4898,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/SyntheticSections.h
> ===================================================================
> RCS file: /home/cvs/src/gnu/llvm/lld/ELF/SyntheticSections.h,v
> diff -u -p -r1.5 SyntheticSections.h
> --- gnu/llvm/lld/ELF/SyntheticSections.h	29 May 2026 11:06:20 -0000	1.5
> +++ gnu/llvm/lld/ELF/SyntheticSections.h	30 Jul 2026 10:58:21 -0000
> @@ -433,6 +433,11 @@ public:
>    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
> @@ -661,6 +666,7 @@ public:
>    void finalizeContents() override;
>    size_t getSize() const override { return getNumSymbols() * entsize; }
>    void addSymbol(Symbol *sym);
> +  void addLocalSectionSymbol(Symbol *sym);
>    unsigned getNumSymbols() const { return symbols.size() + 1; }
>    size_t getSymbolIndex(const Symbol &sym);
>    ArrayRef<SymbolTableEntry> getSymbols() const { return symbols; }
> @@ -670,6 +676,7 @@ protected:
>  
>    // A vector of symbols and their string table offsets.
>    SmallVector<SymbolTableEntry, 0> symbols;
> +  size_t numLocalDynamicSymbols = 0;
>  
>    StringTableSection &strTabSec;
>  
> Index: gnu/llvm/lld/ELF/Target.cpp
> ===================================================================
> RCS file: /home/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	30 Jul 2026 10:58:21 -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: /home/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	30 Jul 2026 10:58:21 -0000
> @@ -22,6 +22,7 @@
>  namespace lld {
>  namespace elf {
>  class Defined;
> +class DynamicReloc;
>  class InputFile;
>  class Symbol;
>  template <class RelTy> struct Relocs;
> @@ -35,6 +36,9 @@ public:
>    virtual RelExpr getRelExpr(RelType type, const Symbol &s,
>                               const uint8_t *loc) const = 0;
>    virtual RelType getDynRel(RelType type) const { return 0; }
> +  virtual void prepareDynamicReloc(RelType type, const Symbol &sym,
> +                                   InputSectionBase &sec) {}
> +  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 {}
> @@ -47,8 +51,13 @@ public:
>    // they are called. This function writes that code.
>    virtual void writePltHeader(uint8_t *buf) const {}
>  
> +  virtual uint64_t getPltEntryOffset(uint32_t pltIdx,
> +                                     uint64_t headerSize) const {
> +    return headerSize + uint64_t{pltIdx} * pltEntrySize;
> +  }
>    virtual void writePlt(uint8_t *buf, const Symbol &sym,
>                          uint64_t pltEntryAddr) const {}
> +  virtual void finalizePlt(uint8_t *buf) const {}
>    virtual void writeIplt(uint8_t *buf, const Symbol &sym,
>                           uint64_t pltEntryAddr) const {
>      // All but PPC32 and PPC64 use the same format for .plt and .iplt entries.
> @@ -135,7 +144,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 +172,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 +198,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: /home/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	30 Jul 2026 10:58:21 -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,15 @@ 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__
> +  // 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;
> Index: gnu/llvm/lld/ELF/Arch/SPARCV9.cpp
> ===================================================================
> RCS file: /home/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	30 Jul 2026 10:58:21 -0000
> @@ -6,10 +6,14 @@
>  //
>  //===----------------------------------------------------------------------===//
>  
> +#include "OutputSections.h"
>  #include "Symbols.h"
>  #include "SyntheticSections.h"
>  #include "Target.h"
> +#include "llvm/ADT/DenseMap.h"
>  #include "llvm/Support/Endian.h"
> +#include <algorithm>
> +#include <cassert>
>  
>  using namespace llvm;
>  using namespace llvm::support::endian;
> @@ -18,15 +22,39 @@ using namespace lld;
>  using namespace lld::elf;
>  
>  namespace {
> +constexpr uint32_t firstLargePltIndex = 32768 - 4;
> +constexpr uint32_t pltLargeEntriesPerBlock = 160;
> +constexpr uint32_t pltLargeCodeSize = 24;
> +constexpr uint32_t pltLargePointerSize = 8;
> +constexpr uint32_t pltLargeBlockSize =
> +    pltLargeEntriesPerBlock * (pltLargeCodeSize + pltLargePointerSize);
> +
>  class SPARCV9 final : public TargetInfo {
>  public:
>    SPARCV9(Ctx &);
>    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,
> +                           InputSectionBase &sec) override;
> +  void finalizeDynamicReloc(DynamicReloc &rel) const override;
> +  void writeGotHeader(uint8_t *buf) const override;
> +  uint64_t getPltEntryOffset(uint32_t pltIdx,
> +                             uint64_t headerSize) const override;
>    void writePlt(uint8_t *buf, const Symbol &sym,
>                  uint64_t pltEntryAddr) const override;
> +  void finalizePlt(uint8_t *buf) 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:
> +  uint64_t getLargePltPointerOffset(uint32_t pltIdx) const;
> +  void relaxGot(uint8_t *loc, const Relocation &rel, uint64_t val) const;
> +
> +  DenseMap<std::pair<OutputSection *, unsigned>, Defined *>
> +      dynamicSectionSymbols;
>  };
>  } // namespace
>  
> @@ -35,9 +63,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 +82,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 +157,255 @@ RelExpr SPARCV9::getRelExpr(RelType type
>    }
>  }
>  
> +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::prepareDynamicReloc(RelType type, const Symbol &sym,
> +                                  InputSectionBase &sec) {
> +  if (auto *eh = dyn_cast<EhInputSection>(&sec)) {
> +    SyntheticSection *parent = eh->getParent();
> +    parent->flags |= sec.flags & SHF_WRITE;
> +    parent->getParent()->flags |= sec.flags & SHF_WRITE;
> +  }
> +
> +  RelType aligned = R_SPARC_NONE, unaligned = R_SPARC_NONE;
> +  uint64_t alignment = 1;
> +  if (!getSparcAbsRelocPair(type, aligned, unaligned, alignment) ||
> +      sym.isPreemptible)
> +    return;
> +
> +  OutputSection *osec = sym.getOutputSection();
> +  if (!osec)
> +    return;
> +
> +  Partition &part = sec.getPartition(ctx);
> +  unsigned partition = part.getNumber(ctx);
> +  auto key = std::make_pair(osec, partition);
> +  if (dynamicSectionSymbols.contains(key))
> +    return;
> +
> +  Defined *sectionSym =
> +      makeDefined(ctx, ctx.internalFile, "", STB_LOCAL, STV_DEFAULT,
> +                  STT_SECTION, 0, 0, osec);
> +  sectionSym->partition = partition;
> +  part.dynSymTab->addLocalSectionSymbol(sectionSym);
> +  dynamicSectionSymbols[key] = sectionSym;
> +}
> +
> +void SPARCV9::finalizeDynamicReloc(DynamicReloc &rel) const {
> +  // scanEhSection records an offset relative to the merged .eh_frame.
> +  if (auto *eh = dyn_cast<EhInputSection>(rel.inputSec)) {
> +    rel.inputSec = eh->getParent();
> +    rel.r_offset = rel.inputSec->getVA(rel.offsetInSec);
> +  }
> +
> +  if (rel.type == R_SPARC_JMP_SLOT && rel.inputSec == ctx.in.plt.get()) {
> +    uint32_t pltIdx = rel.sym->getPltIdx(ctx);
> +    if (pltIdx >= firstLargePltIndex) {
> +      uint64_t off =
> +          getPltEntryOffset(pltIdx, ctx.in.plt->headerSize);
> +      rel.r_offset =
> +          ctx.in.plt->getVA() + getLargePltPointerOffset(pltIdx);
> +      rel.addend = -static_cast<int64_t>(ctx.in.plt->getVA() + off + 4);
> +    }
> +  }
> +
> +  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;
> +  }
> +
> +  OutputSection *osec = rel.sym->getOutputSection();
> +  unsigned partition = rel.inputSec->getPartition(ctx).getNumber(ctx);
> +  auto it = dynamicSectionSymbols.find(std::make_pair(osec, partition));
> +  assert(it != dynamicSectionSymbols.end());
> +  rel.addend = rel.sym->getVA(ctx, rel.addend);
> +  rel.sym = it->second;
> +}
> +
>  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,23 +414,134 @@ 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");
>    }
>  }
>  
> -void SPARCV9::writePlt(uint8_t *buf, const Symbol & /*sym*/,
> +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)
> +    write64(ctx, buf, ctx.mainPart->dynamic->getVA());
> +}
> +
> +uint64_t SPARCV9::getPltEntryOffset(uint32_t pltIdx,
> +                                    uint64_t headerSize) const {
> +  if (pltIdx < firstLargePltIndex)
> +    return headerSize + uint64_t{pltIdx} * pltEntrySize;
> +
> +  uint32_t largeIdx = pltIdx - firstLargePltIndex;
> +  return headerSize + uint64_t{firstLargePltIndex} * pltEntrySize +
> +         largeIdx / pltLargeEntriesPerBlock * pltLargeBlockSize +
> +         largeIdx % pltLargeEntriesPerBlock * pltLargeCodeSize;
> +}
> +
> +uint64_t SPARCV9::getLargePltPointerOffset(uint32_t pltIdx) const {
> +  assert(pltIdx >= firstLargePltIndex);
> +  uint32_t largeIdx = pltIdx - firstLargePltIndex;
> +  uint32_t entriesBeforeBlock =
> +      largeIdx / pltLargeEntriesPerBlock * pltLargeEntriesPerBlock;
> +  uint32_t indexInBlock = largeIdx % pltLargeEntriesPerBlock;
> +  uint64_t largeEntries = ctx.in.plt->getNumEntries() - firstLargePltIndex;
> +  uint64_t entriesInBlock =
> +      std::min<uint64_t>(pltLargeEntriesPerBlock,
> +                         largeEntries - entriesBeforeBlock);
> +  uint64_t blockOffset = getPltEntryOffset(
> +      firstLargePltIndex + entriesBeforeBlock, ctx.in.plt->headerSize);
> +
> +  return blockOffset + entriesInBlock * pltLargeCodeSize +
> +         indexInBlock * pltLargePointerSize;
> +}
> +
> +void SPARCV9::writePlt(uint8_t *buf, const Symbol &sym,
>                         uint64_t pltEntryAddr) const {
> +  uint64_t off = pltEntryAddr - ctx.in.plt->getVA();
> +  if (sym.getPltIdx(ctx) >= firstLargePltIndex) {
> +    uint64_t ptrOff = getLargePltPointerOffset(sym.getPltIdx(ctx));
> +    write32be(buf, 0x8a10000f);      // mov %o7, %g5
> +    write32be(buf + 4, 0x40000002);  // call .+8
> +    write32be(buf + 8, 0x01000000);  // nop
> +    write32be(buf + 12,
> +              0xc25be000 | ((ptrOff - (off + 4)) & 0x1fff));
> +    write32be(buf + 16, 0x83c3c001); // jmpl %o7+%g1, %g1
> +    write32be(buf + 20, 0x9e100005); // mov %g5, %o7
> +    return;
> +  }
> +
>    const uint8_t pltData[] = {
>        0x03, 0x00, 0x00, 0x00, // sethi   (. - .PLT0), %g1
>        0x30, 0x68, 0x00, 0x00, // ba,a    %xcc, .PLT1
> @@ -187,9 +554,15 @@ void SPARCV9::writePlt(uint8_t *buf, con
>    };
>    memcpy(buf, pltData, sizeof(pltData));
>  
> -  uint64_t off = pltEntryAddr - ctx.in.plt->getVA();
>    relocateNoSym(buf, R_SPARC_22, off);
>    relocateNoSym(buf + 4, R_SPARC_WDISP19, -(off + 4 - pltEntrySize));
> +}
> +
> +void SPARCV9::finalizePlt(uint8_t *buf) const {
> +  for (uint32_t i = firstLargePltIndex; i < ctx.in.plt->getNumEntries(); ++i) {
> +    uint64_t off = getPltEntryOffset(i, ctx.in.plt->headerSize);
> +    write64be(buf + getLargePltPointerOffset(i), -(off + 4));
> +  }
>  }
>  
>  void elf::setSPARCV9TargetInfo(Ctx &ctx) { ctx.target.reset(new SPARCV9(ctx)); }
> Index: gnu/llvm/llvm/include/llvm/BinaryFormat/ELF.h
> ===================================================================
> RCS file: /home/cvs/src/gnu/llvm/llvm/include/llvm/BinaryFormat/ELF.h,v
> diff -u -p -r1.8 ELF.h
> --- gnu/llvm/llvm/include/llvm/BinaryFormat/ELF.h	22 Jun 2026 18:17:31 -0000	1.8
> +++ gnu/llvm/llvm/include/llvm/BinaryFormat/ELF.h	30 Jul 2026 10:58:21 -0000
> @@ -1426,6 +1426,9 @@ enum {
>    STT_LOPROC = 13,    // Lowest processor-specific symbol type
>    STT_HIPROC = 15,    // Highest processor-specific symbol type
>  
> +  // SPARC symbol types
> +  STT_SPARC_REGISTER = 13,
> +
>    // AMDGPU symbol types
>    STT_AMDGPU_HSA_KERNEL = 10
>  };
> Index: gnu/llvm/llvm/lib/Target/Sparc/SparcISelLowering.cpp
> ===================================================================
> RCS file: /home/cvs/src/gnu/llvm/llvm/lib/Target/Sparc/SparcISelLowering.cpp,v
> diff -u -p -r1.6 SparcISelLowering.cpp
> --- gnu/llvm/llvm/lib/Target/Sparc/SparcISelLowering.cpp	29 May 2026 11:03:40 -0000	1.6
> +++ gnu/llvm/llvm/lib/Target/Sparc/SparcISelLowering.cpp	30 Jul 2026 10:58:21 -0000
> @@ -1202,16 +1202,43 @@ SparcTargetLowering::LowerCall_32(Target
>  // this table could be generated automatically from RegInfo.
>  Register SparcTargetLowering::getRegisterByName(const char* RegName, LLT VT,
>                                                  const MachineFunction &MF) const {
> -  Register Reg = StringSwitch<Register>(RegName)
> -    .Case("i0", SP::I0).Case("i1", SP::I1).Case("i2", SP::I2).Case("i3", SP::I3)
> -    .Case("i4", SP::I4).Case("i5", SP::I5).Case("i6", SP::I6).Case("i7", SP::I7)
> -    .Case("o0", SP::O0).Case("o1", SP::O1).Case("o2", SP::O2).Case("o3", SP::O3)
> -    .Case("o4", SP::O4).Case("o5", SP::O5).Case("o6", SP::O6).Case("o7", SP::O7)
> -    .Case("l0", SP::L0).Case("l1", SP::L1).Case("l2", SP::L2).Case("l3", SP::L3)
> -    .Case("l4", SP::L4).Case("l5", SP::L5).Case("l6", SP::L6).Case("l7", SP::L7)
> -    .Case("g0", SP::G0).Case("g1", SP::G1).Case("g2", SP::G2).Case("g3", SP::G3)
> -    .Case("g4", SP::G4).Case("g5", SP::G5).Case("g6", SP::G6).Case("g7", SP::G7)
> -    .Default(0);
> +  StringRef Name(RegName);
> +  Name.consume_front("%");
> +
> +  Register Reg = StringSwitch<Register>(Name)
> +                     .Cases({"r24", "i0"}, SP::I0)
> +                     .Cases({"r25", "i1"}, SP::I1)
> +                     .Cases({"r26", "i2"}, SP::I2)
> +                     .Cases({"r27", "i3"}, SP::I3)
> +                     .Cases({"r28", "i4"}, SP::I4)
> +                     .Cases({"r29", "i5"}, SP::I5)
> +                     .Cases({"r30", "i6", "fp"}, SP::I6)
> +                     .Cases({"r31", "i7"}, SP::I7)
> +                     .Cases({"r8", "o0"}, SP::O0)
> +                     .Cases({"r9", "o1"}, SP::O1)
> +                     .Cases({"r10", "o2"}, SP::O2)
> +                     .Cases({"r11", "o3"}, SP::O3)
> +                     .Cases({"r12", "o4"}, SP::O4)
> +                     .Cases({"r13", "o5"}, SP::O5)
> +                     .Cases({"r14", "o6", "sp"}, SP::O6)
> +                     .Cases({"r15", "o7"}, SP::O7)
> +                     .Cases({"r16", "l0"}, SP::L0)
> +                     .Cases({"r17", "l1"}, SP::L1)
> +                     .Cases({"r18", "l2"}, SP::L2)
> +                     .Cases({"r19", "l3"}, SP::L3)
> +                     .Cases({"r20", "l4"}, SP::L4)
> +                     .Cases({"r21", "l5"}, SP::L5)
> +                     .Cases({"r22", "l6"}, SP::L6)
> +                     .Cases({"r23", "l7"}, SP::L7)
> +                     .Cases({"r0", "g0"}, SP::G0)
> +                     .Cases({"r1", "g1"}, SP::G1)
> +                     .Cases({"r2", "g2"}, SP::G2)
> +                     .Cases({"r3", "g3"}, SP::G3)
> +                     .Cases({"r4", "g4"}, SP::G4)
> +                     .Cases({"r5", "g5"}, SP::G5)
> +                     .Cases({"r6", "g6"}, SP::G6)
> +                     .Cases({"r7", "g7"}, SP::G7)
> +                     .Default(0);
>  
>    // If we're directly referencing register names
>    // (e.g in GCC C extension `register int r asm("g1");`),
> Index: gnu/llvm/llvm/lib/Target/Sparc/AsmParser/SparcAsmParser.cpp
> ===================================================================
> RCS file: /home/cvs/src/gnu/llvm/llvm/lib/Target/Sparc/AsmParser/SparcAsmParser.cpp,v
> diff -u -p -r1.1.1.6 SparcAsmParser.cpp
> --- gnu/llvm/llvm/lib/Target/Sparc/AsmParser/SparcAsmParser.cpp	29 May 2026 11:00:49 -0000	1.1.1.6
> +++ gnu/llvm/llvm/lib/Target/Sparc/AsmParser/SparcAsmParser.cpp	30 Jul 2026 10:58:21 -0000
> @@ -39,6 +39,7 @@
>  #include <cassert>
>  #include <cstdint>
>  #include <memory>
> +#include <string>
>  
>  using namespace llvm;
>  
> @@ -81,6 +82,7 @@ class SparcAsmParser : public MCTargetAs
>    bool parseInstruction(ParseInstructionInfo &Info, StringRef Name,
>                          SMLoc NameLoc, OperandVector &Operands) override;
>    ParseStatus parseDirective(AsmToken DirectiveID) override;
> +  bool parsePrimaryExpr(const MCExpr *&Res, SMLoc &EndLoc) override;
>  
>    unsigned validateTargetOperandClass(MCParsedAsmOperand &Op,
>                                        unsigned Kind) override;
> @@ -1044,6 +1046,27 @@ ParseStatus SparcAsmParser::parseDirecti
>      Parser.eatToEndOfStatement();
>      return ParseStatus::Success;
>    }
> +  if (IDVal == ".seg") {
> +    std::string Name;
> +    if (Parser.parseEscapedString(Name) || Parser.parseEOL())
> +      return ParseStatus::Failure;
> +
> +    MCSection *Section;
> +    uint32_t Subsection = 0;
> +    const MCObjectFileInfo *MCOFI = getContext().getObjectFileInfo();
> +    if (Name == "text") {
> +      Section = MCOFI->getTextSection();
> +    } else if (Name == "data" || Name == "data1") {
> +      Section = MCOFI->getDataSection();
> +      Subsection = Name == "data1" ? 1 : 0;
> +    } else if (Name == "bss") {
> +      Section = MCOFI->getBSSSection();
> +    } else {
> +      return Error(DirectiveID.getLoc(), "unknown segment type");
> +    }
> +    getStreamer().switchSection(Section, Subsection);
> +    return ParseStatus::Success;
> +  }
>  
>    // Let the MC layer to handle other directives.
>    return ParseStatus::NoMatch;
> @@ -1686,7 +1709,9 @@ SparcAsmParser::adjustPICRelocation(uint
>    // actually a %pc10 or %pc22 relocation. Otherwise, they are interpreted
>    // as %got10 or %got22 relocation.
>  
> -  if (getContext().getObjectFileInfo()->isPositionIndependent()) {
> +  int64_t AbsoluteValue;
> +  if (getContext().getObjectFileInfo()->isPositionIndependent() &&
> +      !subExpr->evaluateAsAbsolute(AbsoluteValue)) {
>      switch (RelType) {
>      default: break;
>      case ELF::R_SPARC_LO10:
> @@ -1744,6 +1769,16 @@ bool SparcAsmParser::matchSparcAsmModifi
>  
>    EVal = adjustPICRelocation(VK, subExpr);
>    return true;
> +}
> +
> +bool SparcAsmParser::parsePrimaryExpr(const MCExpr *&Res, SMLoc &EndLoc) {
> +  if (Parser.getTok().is(AsmToken::Percent)) {
> +    Parser.Lex();
> +    if (matchSparcAsmModifiers(Res, EndLoc))
> +      return false;
> +    return true;
> +  }
> +  return Parser.parsePrimaryExpr(Res, EndLoc, nullptr);
>  }
>  
>  bool SparcAsmParser::isPossibleExpression(const AsmToken &Token) {

> Index: Makefile
> ===================================================================
> RCS file: /home/cvs/ports/devel/llvm/22/Makefile,v
> diff -u -p -r1.21 Makefile
> --- Makefile	25 Jul 2026 20:07:48 -0000	1.21
> +++ Makefile	29 Jul 2026 19:24:27 -0000
> @@ -2,7 +2,7 @@ LLVM_MAJOR =	22
>  LLVM_VERSION =	${LLVM_MAJOR}.1.8
>  LLVM_PKGSPEC =	>=22,<23
>  
> -REVISION =		3
> +REVISION =	4
>  
>  SHARED_LIBS +=	LLVM		0.0 \
>  		LTO		0.0 \
> Index: patches/patch-lld_ELF_Arch_SPARCV9_cpp
> ===================================================================
> RCS file: patches/patch-lld_ELF_Arch_SPARCV9_cpp
> diff -N patches/patch-lld_ELF_Arch_SPARCV9_cpp
> --- /dev/null	1 Jan 1970 00:00:00 -0000
> +++ patches/patch-lld_ELF_Arch_SPARCV9_cpp	29 Jul 2026 19:22:39 -0000
> @@ -0,0 +1,597 @@
> +Index: lld/ELF/Arch/SPARCV9.cpp
> +--- lld/ELF/Arch/SPARCV9.cpp.orig
> ++++ lld/ELF/Arch/SPARCV9.cpp
> +@@ -6,10 +6,14 @@
> + //
> + //===----------------------------------------------------------------------===//
> + 
> ++#include "OutputSections.h"
> + #include "Symbols.h"
> + #include "SyntheticSections.h"
> + #include "Target.h"
> ++#include "llvm/ADT/DenseMap.h"
> + #include "llvm/Support/Endian.h"
> ++#include <algorithm>
> ++#include <cassert>
> + 
> + using namespace llvm;
> + using namespace llvm::support::endian;
> +@@ -18,15 +22,39 @@ using namespace lld;
> + using namespace lld::elf;
> + 
> + namespace {
> ++constexpr uint32_t firstLargePltIndex = 32768 - 4;
> ++constexpr uint32_t pltLargeEntriesPerBlock = 160;
> ++constexpr uint32_t pltLargeCodeSize = 24;
> ++constexpr uint32_t pltLargePointerSize = 8;
> ++constexpr uint32_t pltLargeBlockSize =
> ++    pltLargeEntriesPerBlock * (pltLargeCodeSize + pltLargePointerSize);
> ++
> + class SPARCV9 final : public TargetInfo {
> + public:
> +   SPARCV9(Ctx &);
> +   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,
> ++                           InputSectionBase &sec) override;
> ++  void finalizeDynamicReloc(DynamicReloc &rel) const override;
> ++  void writeGotHeader(uint8_t *buf) const override;
> ++  uint64_t getPltEntryOffset(uint32_t pltIdx,
> ++                             uint64_t headerSize) const override;
> +   void writePlt(uint8_t *buf, const Symbol &sym,
> +                 uint64_t pltEntryAddr) const override;
> ++  void finalizePlt(uint8_t *buf) 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:
> ++  uint64_t getLargePltPointerOffset(uint32_t pltIdx) const;
> ++  void relaxGot(uint8_t *loc, const Relocation &rel, uint64_t val) const;
> ++
> ++  DenseMap<std::pair<OutputSection *, unsigned>, Defined *>
> ++      dynamicSectionSymbols;
> + };
> + } // namespace
> + 
> +@@ -35,9 +63,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 +82,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 +157,255 @@ RelExpr SPARCV9::getRelExpr(RelType type, const Symbol
> +   }
> + }
> + 
> ++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::prepareDynamicReloc(RelType type, const Symbol &sym,
> ++                                  InputSectionBase &sec) {
> ++  if (auto *eh = dyn_cast<EhInputSection>(&sec)) {
> ++    SyntheticSection *parent = eh->getParent();
> ++    parent->flags |= sec.flags & SHF_WRITE;
> ++    parent->getParent()->flags |= sec.flags & SHF_WRITE;
> ++  }
> ++
> ++  RelType aligned = R_SPARC_NONE, unaligned = R_SPARC_NONE;
> ++  uint64_t alignment = 1;
> ++  if (!getSparcAbsRelocPair(type, aligned, unaligned, alignment) ||
> ++      sym.isPreemptible)
> ++    return;
> ++
> ++  OutputSection *osec = sym.getOutputSection();
> ++  if (!osec)
> ++    return;
> ++
> ++  Partition &part = sec.getPartition(ctx);
> ++  unsigned partition = part.getNumber(ctx);
> ++  auto key = std::make_pair(osec, partition);
> ++  if (dynamicSectionSymbols.contains(key))
> ++    return;
> ++
> ++  Defined *sectionSym =
> ++      makeDefined(ctx, ctx.internalFile, "", STB_LOCAL, STV_DEFAULT,
> ++                  STT_SECTION, 0, 0, osec);
> ++  sectionSym->partition = partition;
> ++  part.dynSymTab->addLocalSectionSymbol(sectionSym);
> ++  dynamicSectionSymbols[key] = sectionSym;
> ++}
> ++
> ++void SPARCV9::finalizeDynamicReloc(DynamicReloc &rel) const {
> ++  // scanEhSection records an offset relative to the merged .eh_frame.
> ++  if (auto *eh = dyn_cast<EhInputSection>(rel.inputSec)) {
> ++    rel.inputSec = eh->getParent();
> ++    rel.r_offset = rel.inputSec->getVA(rel.offsetInSec);
> ++  }
> ++
> ++  if (rel.type == R_SPARC_JMP_SLOT && rel.inputSec == ctx.in.plt.get()) {
> ++    uint32_t pltIdx = rel.sym->getPltIdx(ctx);
> ++    if (pltIdx >= firstLargePltIndex) {
> ++      uint64_t off =
> ++          getPltEntryOffset(pltIdx, ctx.in.plt->headerSize);
> ++      rel.r_offset =
> ++          ctx.in.plt->getVA() + getLargePltPointerOffset(pltIdx);
> ++      rel.addend = -static_cast<int64_t>(ctx.in.plt->getVA() + off + 4);
> ++    }
> ++  }
> ++
> ++  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;
> ++  }
> ++
> ++  OutputSection *osec = rel.sym->getOutputSection();
> ++  unsigned partition = rel.inputSec->getPartition(ctx).getNumber(ctx);
> ++  auto it = dynamicSectionSymbols.find(std::make_pair(osec, partition));
> ++  assert(it != dynamicSectionSymbols.end());
> ++  rel.addend = rel.sym->getVA(ctx, rel.addend);
> ++  rel.sym = it->second;
> ++}
> ++
> + 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_WDISP22:
> ++    // V-disp22
> ++    checkIntUInt(ctx, loc, val, 24, rel);
> ++    write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 2) & 0x003fffff));
> ++    break;
> ++  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_22:
> +     // V-imm22
> +     checkUInt(ctx, loc, val, 22, rel);
> +     write32be(loc, (read32be(loc) & ~0x003fffff) | (val & 0x003fffff));
> +     break;
> +-  case R_SPARC_GOT22:
> +-  case R_SPARC_PC22:
> +-  case R_SPARC_LM22:
> +-    // T-imm22
> +-    write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 10) & 0x003fffff));
> ++  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_HI22:
> +-    // V-imm22
> +-    checkUInt(ctx, loc, val >> 10, 22, rel);
> +-    write32be(loc, (read32be(loc) & ~0x003fffff) | ((val >> 10) & 0x003fffff));
> +-    break;
> +-  case R_SPARC_WDISP19:
> +-    // V-disp19
> +-    checkInt(ctx, loc, val, 21, rel);
> +-    write32be(loc, (read32be(loc) & ~0x0007ffff) | ((val >> 2) & 0x0007ffff));
> +-    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,23 +414,134 @@ void SPARCV9::relocate(uint8_t *loc, const Relocation 
> +     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");
> +   }
> + }
> + 
> +-void SPARCV9::writePlt(uint8_t *buf, const Symbol & /*sym*/,
> ++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)
> ++    write64(ctx, buf, ctx.mainPart->dynamic->getVA());
> ++}
> ++
> ++uint64_t SPARCV9::getPltEntryOffset(uint32_t pltIdx,
> ++                                    uint64_t headerSize) const {
> ++  if (pltIdx < firstLargePltIndex)
> ++    return headerSize + uint64_t{pltIdx} * pltEntrySize;
> ++
> ++  uint32_t largeIdx = pltIdx - firstLargePltIndex;
> ++  return headerSize + uint64_t{firstLargePltIndex} * pltEntrySize +
> ++         largeIdx / pltLargeEntriesPerBlock * pltLargeBlockSize +
> ++         largeIdx % pltLargeEntriesPerBlock * pltLargeCodeSize;
> ++}
> ++
> ++uint64_t SPARCV9::getLargePltPointerOffset(uint32_t pltIdx) const {
> ++  assert(pltIdx >= firstLargePltIndex);
> ++  uint32_t largeIdx = pltIdx - firstLargePltIndex;
> ++  uint32_t entriesBeforeBlock =
> ++      largeIdx / pltLargeEntriesPerBlock * pltLargeEntriesPerBlock;
> ++  uint32_t indexInBlock = largeIdx % pltLargeEntriesPerBlock;
> ++  uint64_t largeEntries = ctx.in.plt->getNumEntries() - firstLargePltIndex;
> ++  uint64_t entriesInBlock =
> ++      std::min<uint64_t>(pltLargeEntriesPerBlock,
> ++                         largeEntries - entriesBeforeBlock);
> ++  uint64_t blockOffset = getPltEntryOffset(
> ++      firstLargePltIndex + entriesBeforeBlock, ctx.in.plt->headerSize);
> ++
> ++  return blockOffset + entriesInBlock * pltLargeCodeSize +
> ++         indexInBlock * pltLargePointerSize;
> ++}
> ++
> ++void SPARCV9::writePlt(uint8_t *buf, const Symbol &sym,
> +                        uint64_t pltEntryAddr) const {
> ++  uint64_t off = pltEntryAddr - ctx.in.plt->getVA();
> ++  if (sym.getPltIdx(ctx) >= firstLargePltIndex) {
> ++    uint64_t ptrOff = getLargePltPointerOffset(sym.getPltIdx(ctx));
> ++    write32be(buf, 0x8a10000f);      // mov %o7, %g5
> ++    write32be(buf + 4, 0x40000002);  // call .+8
> ++    write32be(buf + 8, 0x01000000);  // nop
> ++    write32be(buf + 12,
> ++              0xc25be000 | ((ptrOff - (off + 4)) & 0x1fff));
> ++    write32be(buf + 16, 0x83c3c001); // jmpl %o7+%g1, %g1
> ++    write32be(buf + 20, 0x9e100005); // mov %g5, %o7
> ++    return;
> ++  }
> ++
> +   const uint8_t pltData[] = {
> +       0x03, 0x00, 0x00, 0x00, // sethi   (. - .PLT0), %g1
> +       0x30, 0x68, 0x00, 0x00, // ba,a    %xcc, .PLT1
> +@@ -187,9 +554,15 @@ void SPARCV9::writePlt(uint8_t *buf, const Symbol & /*
> +   };
> +   memcpy(buf, pltData, sizeof(pltData));
> + 
> +-  uint64_t off = pltEntryAddr - ctx.in.plt->getVA();
> +   relocateNoSym(buf, R_SPARC_22, off);
> +   relocateNoSym(buf + 4, R_SPARC_WDISP19, -(off + 4 - pltEntrySize));
> ++}
> ++
> ++void SPARCV9::finalizePlt(uint8_t *buf) const {
> ++  for (uint32_t i = firstLargePltIndex; i < ctx.in.plt->getNumEntries(); ++i) {
> ++    uint64_t off = getPltEntryOffset(i, ctx.in.plt->headerSize);
> ++    write64be(buf + getLargePltPointerOffset(i), -(off + 4));
> ++  }
> + }
> + 
> + void elf::setSPARCV9TargetInfo(Ctx &ctx) { ctx.target.reset(new SPARCV9(ctx)); }
> Index: patches/patch-lld_ELF_Driver_cpp
> ===================================================================
> RCS file: /home/cvs/ports/devel/llvm/22/patches/patch-lld_ELF_Driver_cpp,v
> diff -u -p -r1.2 patch-lld_ELF_Driver_cpp
> --- patches/patch-lld_ELF_Driver_cpp	2 Apr 2026 09:15:11 -0000	1.2
> +++ patches/patch-lld_ELF_Driver_cpp	29 Jul 2026 19:22:39 -0000
> @@ -33,7 +33,21 @@ Index: lld/ELF/Driver.cpp
>     if (ctx.arg.emachine != EM_AARCH64 && ctx.arg.emachine != EM_ARM &&
>         ctx.arg.zExecuteOnlyReport != ReportPolicy::None)
>       ErrAlways(ctx)
> -@@ -1420,8 +1434,6 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &a
> +@@ -1302,9 +1316,10 @@ static SmallVector<StringRef, 0> getSymbolOrderingFile
> + 
> + 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());
> +@@ -1420,8 +1435,6 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &a
>   
>     ctx.e.errorHandlingScript = args.getLastArgValue(OPT_error_handling_script);
>   
> @@ -42,7 +56,7 @@ Index: lld/ELF/Driver.cpp
>     ctx.arg.exportDynamic =
>         args.hasFlag(OPT_export_dynamic, OPT_no_export_dynamic, false) ||
>         args.hasArg(OPT_shared);
> -@@ -1442,8 +1454,15 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &a
> +@@ -1442,8 +1455,15 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &a
>     ctx.arg.icf = getICF(args);
>     ctx.arg.ignoreDataAddressEquality =
>         args.hasArg(OPT_ignore_data_address_equality);
> @@ -58,7 +72,7 @@ Index: lld/ELF/Driver.cpp
>     ctx.arg.init = args.getLastArgValue(OPT_init, "_init");
>     ctx.arg.ltoAAPipeline = args.getLastArgValue(OPT_lto_aa_pipeline);
>     ctx.arg.ltoCSProfileGenerate = args.hasArg(OPT_lto_cs_profile_generate);
> -@@ -1512,7 +1531,12 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &a
> +@@ -1512,7 +1532,12 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &a
>     ctx.arg.outputFile = args.getLastArgValue(OPT_o);
>     if (auto *arg = args.getLastArg(OPT_package_metadata))
>       parsePackageMetadata(ctx, *arg);
> @@ -71,7 +85,7 @@ Index: lld/ELF/Driver.cpp
>     ctx.arg.printIcfSections =
>         args.hasFlag(OPT_print_icf_sections, OPT_no_print_icf_sections, false);
>     if (auto *arg =
> -@@ -1604,7 +1628,11 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &a
> +@@ -1604,7 +1629,11 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &a
>     ctx.arg.trace = args.hasArg(OPT_trace);
>     ctx.arg.undefined = args::getStrings(args, OPT_undefined);
>     ctx.arg.undefinedVersion =
> @@ -83,7 +97,7 @@ Index: lld/ELF/Driver.cpp
>     ctx.arg.unique = args.hasArg(OPT_unique);
>     ctx.arg.useAndroidRelrTags = args.hasFlag(
>         OPT_use_android_relr_tags, OPT_no_use_android_relr_tags, false);
> -@@ -1851,9 +1879,9 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &a
> +@@ -1851,9 +1880,9 @@ static void readConfigs(Ctx &ctx, opt::InputArgList &a
>                        << arg->getValue() << "'";
>       parallel::strategy = hardware_concurrency(threads);
>       ctx.arg.thinLTOJobs = v;
> @@ -96,7 +110,7 @@ Index: lld/ELF/Driver.cpp
>     }
>     if (auto *arg = args.getLastArg(OPT_thinlto_jobs_eq))
>       ctx.arg.thinLTOJobs = arg->getValue();
> -@@ -2070,6 +2098,23 @@ static void setConfigs(Ctx &ctx, opt::InputArgList &ar
> +@@ -2070,6 +2099,23 @@ static void setConfigs(Ctx &ctx, opt::InputArgList &ar
>         ErrAlways(ctx) << "cannot open --why-extract= file " << ctx.arg.whyExtract
>                        << ": " << e.message();
>     }
> @@ -120,7 +134,7 @@ Index: lld/ELF/Driver.cpp
>   }
>   
>   static bool isFormatBinary(Ctx &ctx, StringRef s) {
> -@@ -2234,7 +2279,7 @@ void LinkerDriver::inferMachineType() {
> +@@ -2234,7 +2280,7 @@ void LinkerDriver::inferMachineType() {
>   }
>   
>   // Parse -z max-page-size=<value>. The default value is defined by
> @@ -129,7 +143,7 @@ Index: lld/ELF/Driver.cpp
>   static uint64_t getMaxPageSize(Ctx &ctx, opt::InputArgList &args) {
>     uint64_t val = args::getZOptionValue(args, OPT_z, "max-page-size",
>                                          ctx.target->defaultMaxPageSize);
> -@@ -2252,7 +2297,7 @@ static uint64_t getMaxPageSize(Ctx &ctx, opt::InputArg
> +@@ -2252,7 +2298,7 @@ static uint64_t getMaxPageSize(Ctx &ctx, opt::InputArg
>   }
>   
>   // Parse -z common-page-size=<value>. The default value is defined by
> @@ -138,7 +152,7 @@ Index: lld/ELF/Driver.cpp
>   static uint64_t getCommonPageSize(Ctx &ctx, opt::InputArgList &args) {
>     uint64_t val = args::getZOptionValue(args, OPT_z, "common-page-size",
>                                          ctx.target->defaultCommonPageSize);
> -@@ -2272,6 +2317,16 @@ static uint64_t getCommonPageSize(Ctx &ctx, opt::Input
> +@@ -2272,6 +2318,16 @@ static uint64_t getCommonPageSize(Ctx &ctx, opt::Input
>     return val;
>   }
>   
> @@ -155,7 +169,7 @@ Index: lld/ELF/Driver.cpp
>   // Parses --image-base option.
>   static std::optional<uint64_t> getImageBase(Ctx &ctx, opt::InputArgList &args) {
>     // Because we are using `ctx.arg.maxPageSize` here, this function has to be
> -@@ -3435,6 +3490,11 @@ template <class ELFT> void LinkerDriver::link(opt::Inp
> +@@ -3435,6 +3491,11 @@ template <class ELFT> void LinkerDriver::link(opt::Inp
>     // optimizations such as DATA_SEGMENT_ALIGN in linker scripts. LLD's use of it
>     // is limited to writing trap instructions on the last executable segment.
>     ctx.arg.commonPageSize = getCommonPageSize(ctx, args);
> Index: patches/patch-lld_ELF_InputFiles_cpp
> ===================================================================
> RCS file: /home/cvs/ports/devel/llvm/22/patches/patch-lld_ELF_InputFiles_cpp,v
> diff -u -p -r1.2 patch-lld_ELF_InputFiles_cpp
> --- patches/patch-lld_ELF_InputFiles_cpp	2 Jun 2026 13:16:58 -0000	1.2
> +++ patches/patch-lld_ELF_InputFiles_cpp	29 Jul 2026 19:22:39 -0000
> @@ -43,7 +43,66 @@ Index: lld/ELF/InputFiles.cpp
>         break;
>       case SHT_LLVM_LTO:
>         // Discard .llvm.lto in a relocatable link that does not use the bitcode.
> -@@ -1565,6 +1586,9 @@ template <class ELFT> void SharedFile::parse() {
> +@@ -1192,7 +1213,20 @@ InputSectionBase *ObjFile<ELFT>::createInputSection(ui
> + 
> + // Initialize symbols. symbols is a parallel array to the corresponding ELF
> + // symbol table.
> ++//
> ++// LLVM's SPARC assembler and OpenBSD ld.so do not implement application
> ++// register metadata. Accept GCC scratch declarations, but keep them out of
> ++// ordinary symbol resolution.
> + template <class ELFT>
> ++static bool isSparcRegister(const Ctx &ctx, const typename ELFT::Sym &sym) {
> ++  return ctx.arg.emachine == EM_SPARCV9 && sym.getType() == STT_SPARC_REGISTER;
> ++}
> ++
> ++static bool isSparcScratchRegister(uint64_t value) {
> ++  return value == 2 || value == 3 || value == 6 || value == 7;
> ++}
> ++
> ++template <class ELFT>
> + void ObjFile<ELFT>::initializeSymbols(const object::ELFFile<ELFT> &obj) {
> +   ArrayRef<Elf_Sym> eSyms = this->getELFSyms<ELFT>();
> +   if (!symbols)
> +@@ -1200,14 +1234,25 @@ void ObjFile<ELFT>::initializeSymbols(const object::EL
> + 
> +   // Some entries have been filled by LazyObjFile.
> +   auto *symtab = ctx.symtab.get();
> +-  for (size_t i = firstGlobal, end = eSyms.size(); i != end; ++i)
> +-    if (!symbols[i])
> +-      symbols[i] = symtab->insert(CHECK2(eSyms[i].getName(stringTable), this));
> ++  for (size_t i = firstGlobal, end = eSyms.size(); i != end; ++i) {
> ++    const Elf_Sym &eSym = eSyms[i];
> ++    if (isSparcRegister<ELFT>(ctx, eSym)) {
> ++      StringRef name = CHECK2(eSym.getName(stringTable), this);
> ++      if (eSym.getBinding() != STB_GLOBAL || eSym.st_shndx != SHN_UNDEF ||
> ++          !name.empty() || !isSparcScratchRegister(eSym.st_value))
> ++        Err(ctx) << this << ": unsupported SPARC register declaration";
> ++      symbols[i] = ctx.dummySym;
> ++    } else if (!symbols[i]) {
> ++      symbols[i] = symtab->insert(CHECK2(eSym.getName(stringTable), this));
> ++    }
> ++  }
> + 
> +   // Perform symbol resolution on non-local symbols.
> +   SmallVector<unsigned, 32> undefineds;
> +   for (size_t i = firstGlobal, end = eSyms.size(); i != end; ++i) {
> +     const Elf_Sym &eSym = eSyms[i];
> ++    if (isSparcRegister<ELFT>(ctx, eSym))
> ++      continue;
> +     uint32_t secIdx = eSym.st_shndx;
> +     if (secIdx == SHN_UNDEF) {
> +       undefineds.push_back(i);
> +@@ -1307,6 +1352,8 @@ template <class ELFT> void ObjFile<ELFT>::postParse() 
> +   ArrayRef<Elf_Sym> eSyms = this->getELFSyms<ELFT>();
> +   for (size_t i = firstGlobal, end = eSyms.size(); i != end; ++i) {
> +     const Elf_Sym &eSym = eSyms[i];
> ++    if (isSparcRegister<ELFT>(ctx, eSym))
> ++      continue;
> +     Symbol &sym = *symbols[i];
> +     uint32_t secIdx = eSym.st_shndx;
> +     uint8_t binding = eSym.getBinding();
> +@@ -1565,6 +1612,9 @@ template <class ELFT> void SharedFile::parse() {
>     const ELFFile<ELFT> obj = this->getObj<ELFT>();
>     ArrayRef<Elf_Shdr> sections = getELFShdrs<ELFT>();
>   
> @@ -53,7 +112,7 @@ Index: lld/ELF/InputFiles.cpp
>     const Elf_Shdr *versymSec = nullptr;
>     const Elf_Shdr *verdefSec = nullptr;
>     const Elf_Shdr *verneedSec = nullptr;
> -@@ -1588,6 +1612,13 @@ template <class ELFT> void SharedFile::parse() {
> +@@ -1588,7 +1638,14 @@ template <class ELFT> void SharedFile::parse() {
>       case SHT_GNU_verneed:
>         verneedSec = &sec;
>         break;
> @@ -63,7 +122,18 @@ Index: lld/ELF/InputFiles.cpp
>  +          CHECK2(obj.template getSectionContentsAsArray<char>(sec), this);
>  +      parseGNUWarning(ctx, name, data, sec.sh_size);
>  +      break;
> -+    }
>       }
> ++    }
>     }
>   
> +   if (versymSec && numSymbols == 0) {
> +@@ -1659,6 +1716,9 @@ template <class ELFT> void SharedFile::parse() {
> +   ArrayRef<Elf_Sym> syms = this->getGlobalELFSyms<ELFT>();
> +   for (size_t i = 0, e = syms.size(); i != e; ++i) {
> +     const Elf_Sym &sym = syms[i];
> ++    // A SPARC register declaration is DSO metadata, not a symbol dependency.
> ++    if (isSparcRegister<ELFT>(ctx, sym))
> ++      continue;
> + 
> +     // ELF spec requires that all local symbols precede weak or global
> +     // symbols in each symbol table, and the index of first non-local symbol
> Index: patches/patch-lld_ELF_InputSection_cpp
> ===================================================================
> RCS file: patches/patch-lld_ELF_InputSection_cpp
> diff -N patches/patch-lld_ELF_InputSection_cpp
> --- /dev/null	1 Jan 1970 00:00:00 -0000
> +++ patches/patch-lld_ELF_InputSection_cpp	29 Jul 2026 19:22:39 -0000
> @@ -0,0 +1,11 @@
> +Index: lld/ELF/InputSection.cpp
> +--- lld/ELF/InputSection.cpp.orig
> ++++ lld/ELF/InputSection.cpp
> +@@ -838,6 +838,7 @@ uint64_t InputSectionBase::getRelocTargetVA(Ctx &ctx, 
> +     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: patches/patch-lld_ELF_Relocations_cpp
> ===================================================================
> RCS file: /home/cvs/ports/devel/llvm/22/patches/patch-lld_ELF_Relocations_cpp,v
> diff -u -p -r1.2 patch-lld_ELF_Relocations_cpp
> --- patches/patch-lld_ELF_Relocations_cpp	6 May 2026 12:47:03 -0000	1.2
> +++ patches/patch-lld_ELF_Relocations_cpp	29 Jul 2026 19:22:39 -0000
> @@ -31,17 +31,171 @@ Index: lld/ELF/Relocations.cpp
>   // Construct a message in the following format.
>   //
>   // >>> defined in /home/alice/src/foo.o
> -@@ -681,6 +704,13 @@ bool RelocScan::maybeReportUndefined(Undefined &sym, u
> -   // .got2
> +@@ -147,8 +170,9 @@ bool lld::elf::needsGot(RelExpr expr) {
> + static bool isRelExpr(RelExpr expr) {
> +   return oneof<R_PC, R_GOTREL, R_GOTPLTREL, RE_ARM_PCA, RE_MIPS_GOTREL,
> +                RE_PPC64_CALL, RE_PPC64_RELAX_TOC, RE_AARCH64_PAGE_PC,
> +-               R_RELAX_GOT_PC, RE_RISCV_PC_INDIRECT, RE_PPC64_RELAX_GOT_PC,
> +-               RE_LOONGARCH_PAGE_PC, RE_LOONGARCH_PC_INDIRECT>(expr);
> ++               R_RELAX_GOT_OFF, R_RELAX_GOT_PC, RE_RISCV_PC_INDIRECT,
> ++               RE_PPC64_RELAX_GOT_PC, RE_LOONGARCH_PAGE_PC,
> ++               RE_LOONGARCH_PC_INDIRECT>(expr);
> + }
> + 
> + static RelExpr toPlt(RelExpr expr) {
> +@@ -682,6 +706,13 @@ bool RelocScan::maybeReportUndefined(Undefined &sym, u
>     if (sym.discardedSecIdx != 0 && (sec->name == ".got2" || sec->name == ".toc"))
>       return false;
> -+
> + 
>  +#ifdef __OpenBSD__
>  +  // GCC (at least 8 and 11) can produce a ".gcc_except_table" with relocations
>  +  // to discarded sections on riscv64
>  +  if (sym.discardedSecIdx != 0 && sec->name == ".gcc_except_table")
>  +    return false;
>  +#endif
> - 
> ++
>     bool isWarning =
>         (ctx.arg.unresolvedSymbols == UnresolvedPolicy::Warn && canBeExternal) ||
> +       ctx.arg.noinhibitExec;
> +@@ -740,14 +771,18 @@ static void addRelativeReloc(Ctx &ctx, InputSectionBas
> + 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)
> ++  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), true, sym, 0, R_ADDEND});
> +-  else
> +-    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) {
> +@@ -915,25 +950,32 @@ void RelocScan::process(RelExpr expr, RelType type, ui
> +   // 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);
> +     }
> +   }
> +@@ -992,8 +1034,14 @@ void RelocScan::process(RelExpr expr, RelType type, ui
> +                     (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 && !sym.isPreemptible)) {
> ++    bool useRelative =
> ++        rel == ctx.target->symbolicRel && !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);
> +       return;
> +     }
> +@@ -1036,6 +1084,7 @@ void RelocScan::process(RelExpr expr, RelType type, ui
> +           return;
> +         }
> +       }
> ++      ctx.target->prepareDynamicReloc(rel, sym, *sec);
> +       part.relaDyn->addSymbolReloc(rel, *sec, offset, sym, addend, type);
> + 
> +       // MIPS ABI turns using of GOT and dynamic relocations inside out.
> +@@ -1206,12 +1255,13 @@ unsigned RelocScan::handleTlsRelocation(RelExpr expr, 
> + 
> +   // 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;
> +@@ -1555,10 +1605,7 @@ void elf::postScanRelocations(Ctx &ctx) {
> +       } else {
> +         assert(sym.isFunc() && sym.hasFlag(NEEDS_PLT));
> +         if (!sym.isDefined()) {
> +-          replaceWithDefined(ctx, sym, *ctx.in.plt,
> +-                             ctx.target->pltHeaderSize +
> +-                                 ctx.target->pltEntrySize * sym.getPltIdx(ctx),
> +-                             0);
> ++          replaceWithDefined(ctx, sym, *ctx.in.plt, sym.getPltOffset(ctx), 0);
> +           sym.setFlags(NEEDS_COPY);
> +           if (ctx.arg.emachine == EM_PPC) {
> +             // PPC32 canonical PLT entries are at the beginning of .glink
> +@@ -2203,11 +2250,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) {
> +@@ -2221,7 +2268,7 @@ bool elf::hexagonNeedsTLSSymbol(ArrayRef<OutputSection
> +   return needTlsSymbol;
> + }
> + 
> +-void elf::hexagonTLSSymbolUpdate(Ctx &ctx) {
> ++void elf::tlsSymbolUpdate(Ctx &ctx) {
> +   Symbol *sym = ctx.symtab->find("__tls_get_addr");
> +   if (!sym)
> +     return;
> Index: patches/patch-lld_ELF_Relocations_h
> ===================================================================
> RCS file: /home/cvs/ports/devel/llvm/22/patches/patch-lld_ELF_Relocations_h,v
> diff -u -p -r1.1 patch-lld_ELF_Relocations_h
> --- patches/patch-lld_ELF_Relocations_h	6 May 2026 12:47:03 -0000	1.1
> +++ patches/patch-lld_ELF_Relocations_h	29 Jul 2026 19:22:39 -0000
> @@ -1,13 +1,27 @@
>  Index: lld/ELF/Relocations.h
>  --- lld/ELF/Relocations.h.orig
>  +++ lld/ELF/Relocations.h
> -@@ -176,6 +176,9 @@ bool hexagonNeedsTLSSymbol(ArrayRef<OutputSection *> o
> +@@ -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,10 +172,13 @@ bool maybeReportUndefined(Ctx &, Undefined &sym, Input
> + void postScanRelocations(Ctx &ctx);
> + void addGotEntry(Ctx &ctx, Symbol &sym);
>   
> - bool isAbsolute(const 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);
> ++
>  +void reportGNUWarning(Ctx &ctx, Symbol &sym, InputSectionBase &sec,
>  +                      uint64_t offset);
> -+
> + 
>   class ThunkSection;
>   class Thunk;
> - class InputSectionDescription;
> Index: patches/patch-lld_ELF_Symbols_cpp
> ===================================================================
> RCS file: /home/cvs/ports/devel/llvm/22/patches/patch-lld_ELF_Symbols_cpp,v
> diff -u -p -r1.1.1.1 patch-lld_ELF_Symbols_cpp
> --- patches/patch-lld_ELF_Symbols_cpp	25 Feb 2026 13:31:43 -0000	1.1.1.1
> +++ patches/patch-lld_ELF_Symbols_cpp	29 Jul 2026 19:22:39 -0000
> @@ -12,3 +12,26 @@ Index: lld/ELF/Symbols.cpp
>   
>   template <typename T> struct AssertSymbol {
>     static_assert(std::is_trivially_destructible<T>(),
> +@@ -173,11 +173,20 @@ 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.target->getPltEntryOffset(getPltIdx(ctx),
> ++                                       ctx.in.plt->headerSize);
> ++}
> ++
> + uint64_t Symbol::getPltVA(Ctx &ctx) const {
> +   uint64_t outVA = isInIplt ? ctx.in.iplt->getVA() +
> +                                   getPltIdx(ctx) * ctx.target->ipltEntrySize
> +-                            : ctx.in.plt->getVA() + ctx.in.plt->headerSize +
> +-                                  getPltIdx(ctx) * ctx.target->pltEntrySize;
> ++                            : ctx.in.plt->getVA() +
> ++                                  ctx.target->getPltEntryOffset(
> ++                                      getPltIdx(ctx),
> ++                                      ctx.in.plt->headerSize);
> + 
> +   // While linking microMIPS code PLT code are always microMIPS
> +   // code. Set the less-significant bit to track that fact.
> Index: patches/patch-lld_ELF_Symbols_h
> ===================================================================
> RCS file: /home/cvs/ports/devel/llvm/22/patches/patch-lld_ELF_Symbols_h,v
> diff -u -p -r1.4 patch-lld_ELF_Symbols_h
> --- patches/patch-lld_ELF_Symbols_h	10 Jul 2026 06:50:19 -0000	1.4
> +++ patches/patch-lld_ELF_Symbols_h	29 Jul 2026 19:22:39 -0000
> @@ -11,7 +11,15 @@ Index: lld/ELF/Symbols.h
>     // Symbol visibility. This is the computed minimum visibility of all
>     // observed non-DSO symbols.
>     uint8_t visibility() const { return stOther & 3; }
> -@@ -244,7 +247,7 @@ class Symbol { (public)
> +@@ -204,6 +207,7 @@ class Symbol { (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;
> +@@ -244,7 +248,7 @@ class Symbol { (public)
>         : file(file), nameData(name.data()), nameSize(name.size()), type(type),
>           binding(binding), stOther(stOther), symbolKind(k), isPreemptible(false),
>           isUsedInRegularObj(false), used(false), isExported(false),
> @@ -20,7 +28,7 @@ Index: lld/ELF/Symbols.h
>           isInIplt(false), gotInIgot(false), folded(false),
>           archSpecificBit(false), scriptDefined(false), dsoDefined(false),
>           dsoProtected(false), versionScriptAssigned(false), thunkAccessed(false),
> -@@ -540,6 +543,8 @@ void reportDuplicate(Ctx &, const Symbol &sym, const I
> +@@ -540,6 +544,8 @@ void reportDuplicate(Ctx &, const Symbol &sym, const I
>   void maybeWarnUnorderableSymbol(Ctx &, const Symbol *sym);
>   bool computeIsPreemptible(Ctx &, const Symbol &sym);
>   void parseVersionAndComputeIsPreemptible(Ctx &);
> Index: patches/patch-lld_ELF_SyntheticSections_cpp
> ===================================================================
> RCS file: /home/cvs/ports/devel/llvm/22/patches/patch-lld_ELF_SyntheticSections_cpp,v
> diff -u -p -r1.1.1.1 patch-lld_ELF_SyntheticSections_cpp
> --- patches/patch-lld_ELF_SyntheticSections_cpp	25 Feb 2026 13:31:43 -0000	1.1.1.1
> +++ patches/patch-lld_ELF_SyntheticSections_cpp	29 Jul 2026 19:22:39 -0000
> @@ -1,7 +1,98 @@
>  Index: lld/ELF/SyntheticSections.cpp
>  --- lld/ELF/SyntheticSections.cpp.orig
>  +++ lld/ELF/SyntheticSections.cpp
> -@@ -2602,6 +2602,10 @@ PltSection::PltSection(Ctx &ctx)
> +@@ -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,20 @@ 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();
> ++  ctx.target->finalizeDynamicReloc(*this);
> +   r_sym = getSymIndex(symt);
> +   addend = computeAddend(ctx);
> +   isFinal = true; // Catch errors
> +@@ -2176,12 +2183,12 @@ 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.
> ++  // If it is a .dynsym, 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;
> ++  // Because the first symbol entry is a null entry, add one to the number of
> ++  // local symbols.
> ++  getParent()->info = numLocalDynamicSymbols + 1;
> + 
> +   if (getPartition(ctx).gnuHashTab) {
> +     // NB: It also sorts Symbols to meet the GNU hash table requirements.
> +@@ -2201,7 +2208,7 @@ void SymbolTableBaseSection::finalizeContents() {
> + 
> + // The ELF spec requires that all local symbols precede global symbols, so we
> + // sort symbol entries in this function. (For .dynsym, we don't do that because
> +-// symbols for dynamic linking are inherently all globals.)
> ++// local symbols are inserted before global symbols.)
> + //
> + // Aside from above, we put local symbols in groups starting with the STT_FILE
> + // symbol. That is convenient for purpose of identifying where are local symbols
> +@@ -2235,6 +2242,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.mainPart->dynSymTab.get())
> +     return sym.dynsymIndex;
> +@@ -2508,7 +2522,8 @@ void GnuHashTableSection::addSymbols(SmallVectorImpl<S
> +   // its type correctly.
> +   auto mid =
> +       std::stable_partition(v.begin(), v.end(), [&](const SymbolTableEntry &s) {
> +-        return !s.sym->isDefined() || s.sym->partition != partition;
> ++        return s.sym->isLocal() || !s.sym->isDefined() ||
> ++               s.sym->partition != partition;
> +       });
> + 
> +   // We chose load factor 4 for the on-disk hash table. For each hash
> +@@ -2574,6 +2589,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;
> +@@ -2602,23 +2619,29 @@ PltSection::PltSection(Ctx &ctx)
>     if ((ctx.arg.emachine == EM_386 || ctx.arg.emachine == EM_X86_64) &&
>         (ctx.arg.andFeatures & GNU_PROPERTY_X86_FEATURE_1_IBT))
>       name = ".plt.sec";
> @@ -12,7 +103,58 @@ Index: lld/ELF/SyntheticSections.cpp
>   
>     // The PLT needs to be writable on SPARC as the dynamic linker will
>     // modify the instructions in the PLT entries.
> -@@ -4909,6 +4913,12 @@ template <class ELFT> void elf::createSyntheticSection
> +-  if (ctx.arg.emachine == EM_SPARCV9)
> ++  if (ctx.arg.emachine == EM_SPARCV9) {
> +     this->flags |= SHF_WRITE;
> ++    addralign = 256;
> ++  }
> + }
> + 
> + void PltSection::writeTo(uint8_t *buf) {
> +   // At beginning of PLT, we have code to call the dynamic
> +   // linker to resolve dynsyms at runtime. Write such code.
> +   ctx.target->writePltHeader(buf);
> +-  size_t off = headerSize;
> + 
> +   for (const Symbol *sym : entries) {
> ++    size_t off = sym->getPltOffset(ctx);
> +     ctx.target->writePlt(buf + off, *sym, getVA() + off);
> +-    off += ctx.target->pltEntrySize;
> +   }
> ++  ctx.target->finalizePlt(buf);
> + }
> + 
> + void PltSection::addEntry(Symbol &sym) {
> +@@ -2641,11 +2664,8 @@ bool PltSection::isNeeded() const {
> + void PltSection::addSymbols() {
> +   ctx.target->addPltHeaderSymbols(*this);
> + 
> +-  size_t off = headerSize;
> +-  for (size_t i = 0; i < entries.size(); ++i) {
> +-    ctx.target->addPltSymbols(*this, off);
> +-    off += ctx.target->pltEntrySize;
> +-  }
> ++  for (const Symbol *sym : entries)
> ++    ctx.target->addPltSymbols(*this, sym->getPltOffset(ctx));
> + }
> + 
> + IpltSection::IpltSection(Ctx &ctx)
> +@@ -4878,10 +4898,12 @@ template <class ELFT> void elf::createSyntheticSection
> +   // _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.
> +@@ -4909,6 +4931,12 @@ template <class ELFT> void elf::createSyntheticSection
>       ctx.in.gnuProperty = std::make_unique<GnuPropertySection>(ctx);
>       add(*ctx.in.gnuProperty);
>     }
> Index: patches/patch-lld_ELF_SyntheticSections_h
> ===================================================================
> RCS file: /home/cvs/ports/devel/llvm/22/patches/patch-lld_ELF_SyntheticSections_h,v
> diff -u -p -r1.1.1.1 patch-lld_ELF_SyntheticSections_h
> --- patches/patch-lld_ELF_SyntheticSections_h	25 Feb 2026 13:31:43 -0000	1.1.1.1
> +++ patches/patch-lld_ELF_SyntheticSections_h	29 Jul 2026 19:22:39 -0000
> @@ -1,7 +1,35 @@
>  Index: lld/ELF/SyntheticSections.h
>  --- lld/ELF/SyntheticSections.h.orig
>  +++ lld/ELF/SyntheticSections.h
> -@@ -1462,7 +1462,11 @@ void addVerneed(Ctx &, Symbol &ss);
> +@@ -433,6 +433,11 @@ class DynamicReloc { (public)
> +   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
> +@@ -661,6 +666,7 @@ class SymbolTableBaseSection : public SyntheticSection
> +   void finalizeContents() override;
> +   size_t getSize() const override { return getNumSymbols() * entsize; }
> +   void addSymbol(Symbol *sym);
> ++  void addLocalSectionSymbol(Symbol *sym);
> +   unsigned getNumSymbols() const { return symbols.size() + 1; }
> +   size_t getSymbolIndex(const Symbol &sym);
> +   ArrayRef<SymbolTableEntry> getSymbols() const { return symbols; }
> +@@ -670,6 +676,7 @@ class SymbolTableBaseSection : public SyntheticSection
> + 
> +   // A vector of symbols and their string table offsets.
> +   SmallVector<SymbolTableEntry, 0> symbols;
> ++  size_t numLocalDynamicSymbols = 0;
> + 
> +   StringTableSection &strTabSec;
> + 
> +@@ -1462,7 +1469,11 @@ void addVerneed(Ctx &, Symbol &ss);
>   // placed in it.
>   struct PhdrEntry {
>     PhdrEntry(Ctx &ctx, unsigned type, unsigned flags)
> Index: patches/patch-lld_ELF_Target_cpp
> ===================================================================
> RCS file: patches/patch-lld_ELF_Target_cpp
> diff -N patches/patch-lld_ELF_Target_cpp
> --- /dev/null	1 Jan 1970 00:00:00 -0000
> +++ patches/patch-lld_ELF_Target_cpp	29 Jul 2026 19:22:39 -0000
> @@ -0,0 +1,16 @@
> +Index: lld/ELF/Target.cpp
> +--- lld/ELF/Target.cpp.orig
> ++++ lld/ELF/Target.cpp
> +@@ -158,6 +158,12 @@ RelExpr TargetInfo::adjustGotPcExpr(RelType type, int6
> +   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: patches/patch-lld_ELF_Target_h
> ===================================================================
> RCS file: patches/patch-lld_ELF_Target_h
> diff -N patches/patch-lld_ELF_Target_h
> --- /dev/null	1 Jan 1970 00:00:00 -0000
> +++ patches/patch-lld_ELF_Target_h	29 Jul 2026 19:22:39 -0000
> @@ -0,0 +1,63 @@
> +Index: lld/ELF/Target.h
> +--- lld/ELF/Target.h.orig
> ++++ 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;
> +@@ -35,6 +36,9 @@ class TargetInfo { (public)
> +   virtual RelExpr getRelExpr(RelType type, const Symbol &s,
> +                              const uint8_t *loc) const = 0;
> +   virtual RelType getDynRel(RelType type) const { return 0; }
> ++  virtual void prepareDynamicReloc(RelType type, const Symbol &sym,
> ++                                   InputSectionBase &sec) {}
> ++  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 {}
> +@@ -47,8 +51,13 @@ class TargetInfo { (public)
> +   // they are called. This function writes that code.
> +   virtual void writePltHeader(uint8_t *buf) const {}
> + 
> ++  virtual uint64_t getPltEntryOffset(uint32_t pltIdx,
> ++                                     uint64_t headerSize) const {
> ++    return headerSize + uint64_t{pltIdx} * pltEntrySize;
> ++  }
> +   virtual void writePlt(uint8_t *buf, const Symbol &sym,
> +                         uint64_t pltEntryAddr) const {}
> ++  virtual void finalizePlt(uint8_t *buf) const {}
> +   virtual void writeIplt(uint8_t *buf, const Symbol &sym,
> +                          uint64_t pltEntryAddr) const {
> +     // All but PPC32 and PPC64 use the same format for .plt and .iplt entries.
> +@@ -135,7 +144,8 @@ class TargetInfo { (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 +172,8 @@ class TargetInfo { (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 +198,8 @@ class TargetInfo { (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: patches/patch-lld_ELF_Writer_cpp
> ===================================================================
> RCS file: /home/cvs/ports/devel/llvm/22/patches/patch-lld_ELF_Writer_cpp,v
> diff -u -p -r1.1.1.1 patch-lld_ELF_Writer_cpp
> --- patches/patch-lld_ELF_Writer_cpp	25 Feb 2026 13:31:43 -0000	1.1.1.1
> +++ patches/patch-lld_ELF_Writer_cpp	29 Jul 2026 19:22:39 -0000
> @@ -18,10 +18,12 @@ Index: lld/ELF/Writer.cpp
>         (ctx.arg.discard == DiscardPolicy::Locals ||
>          (sym.section && (sym.section->flags & SHF_MERGE))))
>       return false;
> -@@ -625,7 +626,11 @@ static bool isRelroSection(Ctx &ctx, const OutputSecti
> +@@ -624,8 +625,12 @@ static bool isRelroSection(Ctx &ctx, const OutputSecti
> +   // 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 (sec == ctx.in.gotPlt->getParent())
> ++  if (ctx.target->usesGotPlt && sec == ctx.in.gotPlt->getParent())
>  +#ifndef __OpenBSD__
>       return ctx.arg.zNow;
>  +#else
> @@ -43,7 +45,24 @@ Index: lld/ELF/Writer.cpp
>   
>     return abiAgnostic || abiSpecific;
>   }
> -@@ -927,6 +935,9 @@ template <class ELFT> void Writer<ELFT>::setReservedSy
> +@@ -855,10 +863,14 @@ template <class ELFT> void Writer<ELFT>::setReservedSy
> +   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;
> +   }
> + 
> +@@ -927,6 +939,9 @@ template <class ELFT> void Writer<ELFT>::setReservedSy
>       ctx.sym.bss->section = sbss ? sbss : findSection(ctx, ".bss");
>     }
>   
> @@ -53,7 +72,50 @@ Index: lld/ELF/Writer.cpp
>     // Setup MIPS _gp_disp/__gnu_local_gp symbols which should
>     // be equal to the _gp symbol's value.
>     if (ctx.sym.mipsGp) {
> -@@ -2454,34 +2465,22 @@ Writer<ELFT>::createPhdrs(Partition &part) {
> +@@ -1533,9 +1548,9 @@ template <class ELFT> void Writer<ELFT>::finalizeAddre
> +   };
> +   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);
> +@@ -2031,10 +2046,10 @@ template <class ELFT> void Writer<ELFT>::finalizeSecti
> +       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});
> +@@ -2300,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;
> +@@ -2454,34 +2478,22 @@ Writer<ELFT>::createPhdrs(Partition &part) {
>       addHdr(PT_GNU_EH_FRAME, part.ehFrameHdr->getParent()->getPhdrFlags())
>           ->add(part.ehFrameHdr->getParent());
>   
> @@ -101,7 +163,7 @@ Index: lld/ELF/Writer.cpp
>     // PT_OPENBSD_NOBTCFI is an OpenBSD-specific header to mark that the
>     // executable is expected to violate branch-target CFI checks.
>     if (ctx.arg.zNoBtCfi)
> -@@ -2493,7 +2492,19 @@ Writer<ELFT>::createPhdrs(Partition &part) {
> +@@ -2493,7 +2505,19 @@ Writer<ELFT>::createPhdrs(Partition &part) {
>     // OpenBSD.
>     if (ctx.arg.zWxneeded)
>       addHdr(PT_OPENBSD_WXNEEDED, PF_X);
> @@ -121,7 +183,7 @@ Index: lld/ELF/Writer.cpp
>     if (OutputSection *cmd = findSection(ctx, ".note.gnu.property", partNo))
>       addHdr(PT_GNU_PROPERTY, PF_R)->add(cmd);
>   
> -@@ -2586,6 +2597,31 @@ template <class ELFT> void Writer<ELFT>::fixSectionAli
> +@@ -2586,6 +2610,31 @@ template <class ELFT> void Writer<ELFT>::fixSectionAli
>           };
>       }
>     };
> Index: patches/patch-llvm_include_llvm_BinaryFormat_ELF_h
> ===================================================================
> RCS file: patches/patch-llvm_include_llvm_BinaryFormat_ELF_h
> diff -N patches/patch-llvm_include_llvm_BinaryFormat_ELF_h
> --- /dev/null	1 Jan 1970 00:00:00 -0000
> +++ patches/patch-llvm_include_llvm_BinaryFormat_ELF_h	29 Jul 2026 19:22:39 -0000
> @@ -0,0 +1,13 @@
> +Index: llvm/include/llvm/BinaryFormat/ELF.h
> +--- llvm/include/llvm/BinaryFormat/ELF.h.orig
> ++++ llvm/include/llvm/BinaryFormat/ELF.h
> +@@ -1426,6 +1426,9 @@ enum {
> +   STT_LOPROC = 13,    // Lowest processor-specific symbol type
> +   STT_HIPROC = 15,    // Highest processor-specific symbol type
> + 
> ++  // SPARC symbol types
> ++  STT_SPARC_REGISTER = 13,
> ++
> +   // AMDGPU symbol types
> +   STT_AMDGPU_HSA_KERNEL = 10
> + };
> Index: patches/patch-llvm_lib_Target_Sparc_AsmParser_SparcAsmParser_cpp
> ===================================================================
> RCS file: patches/patch-llvm_lib_Target_Sparc_AsmParser_SparcAsmParser_cpp
> diff -N patches/patch-llvm_lib_Target_Sparc_AsmParser_SparcAsmParser_cpp
> --- /dev/null	1 Jan 1970 00:00:00 -0000
> +++ patches/patch-llvm_lib_Target_Sparc_AsmParser_SparcAsmParser_cpp	29 Jul 2026 19:22:39 -0000
> @@ -0,0 +1,76 @@
> +Index: llvm/lib/Target/Sparc/AsmParser/SparcAsmParser.cpp
> +--- llvm/lib/Target/Sparc/AsmParser/SparcAsmParser.cpp.orig
> ++++ llvm/lib/Target/Sparc/AsmParser/SparcAsmParser.cpp
> +@@ -39,6 +39,7 @@
> + #include <cassert>
> + #include <cstdint>
> + #include <memory>
> ++#include <string>
> + 
> + using namespace llvm;
> + 
> +@@ -81,6 +82,7 @@ class SparcAsmParser : public MCTargetAsmParser {
> +   bool parseInstruction(ParseInstructionInfo &Info, StringRef Name,
> +                         SMLoc NameLoc, OperandVector &Operands) override;
> +   ParseStatus parseDirective(AsmToken DirectiveID) override;
> ++  bool parsePrimaryExpr(const MCExpr *&Res, SMLoc &EndLoc) override;
> + 
> +   unsigned validateTargetOperandClass(MCParsedAsmOperand &Op,
> +                                       unsigned Kind) override;
> +@@ -1044,7 +1046,28 @@ ParseStatus SparcAsmParser::parseDirective(AsmToken Di
> +     Parser.eatToEndOfStatement();
> +     return ParseStatus::Success;
> +   }
> ++  if (IDVal == ".seg") {
> ++    std::string Name;
> ++    if (Parser.parseEscapedString(Name) || Parser.parseEOL())
> ++      return ParseStatus::Failure;
> + 
> ++    MCSection *Section;
> ++    uint32_t Subsection = 0;
> ++    const MCObjectFileInfo *MCOFI = getContext().getObjectFileInfo();
> ++    if (Name == "text") {
> ++      Section = MCOFI->getTextSection();
> ++    } else if (Name == "data" || Name == "data1") {
> ++      Section = MCOFI->getDataSection();
> ++      Subsection = Name == "data1" ? 1 : 0;
> ++    } else if (Name == "bss") {
> ++      Section = MCOFI->getBSSSection();
> ++    } else {
> ++      return Error(DirectiveID.getLoc(), "unknown segment type");
> ++    }
> ++    getStreamer().switchSection(Section, Subsection);
> ++    return ParseStatus::Success;
> ++  }
> ++
> +   // Let the MC layer to handle other directives.
> +   return ParseStatus::NoMatch;
> + }
> +@@ -1686,7 +1709,9 @@ SparcAsmParser::adjustPICRelocation(uint16_t RelType, 
> +   // actually a %pc10 or %pc22 relocation. Otherwise, they are interpreted
> +   // as %got10 or %got22 relocation.
> + 
> +-  if (getContext().getObjectFileInfo()->isPositionIndependent()) {
> ++  int64_t AbsoluteValue;
> ++  if (getContext().getObjectFileInfo()->isPositionIndependent() &&
> ++      !subExpr->evaluateAsAbsolute(AbsoluteValue)) {
> +     switch (RelType) {
> +     default: break;
> +     case ELF::R_SPARC_LO10:
> +@@ -1744,6 +1769,16 @@ bool SparcAsmParser::matchSparcAsmModifiers(const MCEx
> + 
> +   EVal = adjustPICRelocation(VK, subExpr);
> +   return true;
> ++}
> ++
> ++bool SparcAsmParser::parsePrimaryExpr(const MCExpr *&Res, SMLoc &EndLoc) {
> ++  if (Parser.getTok().is(AsmToken::Percent)) {
> ++    Parser.Lex();
> ++    if (matchSparcAsmModifiers(Res, EndLoc))
> ++      return false;
> ++    return true;
> ++  }
> ++  return Parser.parsePrimaryExpr(Res, EndLoc, nullptr);
> + }
> + 
> + bool SparcAsmParser::isPossibleExpression(const AsmToken &Token) {
> Index: patches/patch-llvm_lib_Target_Sparc_SparcISelLowering_cpp
> ===================================================================
> RCS file: patches/patch-llvm_lib_Target_Sparc_SparcISelLowering_cpp
> diff -N patches/patch-llvm_lib_Target_Sparc_SparcISelLowering_cpp
> --- /dev/null	1 Jan 1970 00:00:00 -0000
> +++ patches/patch-llvm_lib_Target_Sparc_SparcISelLowering_cpp	29 Jul 2026 19:22:39 -0000
> @@ -0,0 +1,57 @@
> +Index: llvm/lib/Target/Sparc/SparcISelLowering.cpp
> +--- llvm/lib/Target/Sparc/SparcISelLowering.cpp.orig
> ++++ llvm/lib/Target/Sparc/SparcISelLowering.cpp
> +@@ -1202,16 +1202,43 @@ SparcTargetLowering::LowerCall_32(TargetLowering::Call
> + // this table could be generated automatically from RegInfo.
> + Register SparcTargetLowering::getRegisterByName(const char* RegName, LLT VT,
> +                                                 const MachineFunction &MF) const {
> +-  Register Reg = StringSwitch<Register>(RegName)
> +-    .Case("i0", SP::I0).Case("i1", SP::I1).Case("i2", SP::I2).Case("i3", SP::I3)
> +-    .Case("i4", SP::I4).Case("i5", SP::I5).Case("i6", SP::I6).Case("i7", SP::I7)
> +-    .Case("o0", SP::O0).Case("o1", SP::O1).Case("o2", SP::O2).Case("o3", SP::O3)
> +-    .Case("o4", SP::O4).Case("o5", SP::O5).Case("o6", SP::O6).Case("o7", SP::O7)
> +-    .Case("l0", SP::L0).Case("l1", SP::L1).Case("l2", SP::L2).Case("l3", SP::L3)
> +-    .Case("l4", SP::L4).Case("l5", SP::L5).Case("l6", SP::L6).Case("l7", SP::L7)
> +-    .Case("g0", SP::G0).Case("g1", SP::G1).Case("g2", SP::G2).Case("g3", SP::G3)
> +-    .Case("g4", SP::G4).Case("g5", SP::G5).Case("g6", SP::G6).Case("g7", SP::G7)
> +-    .Default(0);
> ++  StringRef Name(RegName);
> ++  Name.consume_front("%");
> ++
> ++  Register Reg = StringSwitch<Register>(Name)
> ++                     .Cases({"r24", "i0"}, SP::I0)
> ++                     .Cases({"r25", "i1"}, SP::I1)
> ++                     .Cases({"r26", "i2"}, SP::I2)
> ++                     .Cases({"r27", "i3"}, SP::I3)
> ++                     .Cases({"r28", "i4"}, SP::I4)
> ++                     .Cases({"r29", "i5"}, SP::I5)
> ++                     .Cases({"r30", "i6", "fp"}, SP::I6)
> ++                     .Cases({"r31", "i7"}, SP::I7)
> ++                     .Cases({"r8", "o0"}, SP::O0)
> ++                     .Cases({"r9", "o1"}, SP::O1)
> ++                     .Cases({"r10", "o2"}, SP::O2)
> ++                     .Cases({"r11", "o3"}, SP::O3)
> ++                     .Cases({"r12", "o4"}, SP::O4)
> ++                     .Cases({"r13", "o5"}, SP::O5)
> ++                     .Cases({"r14", "o6", "sp"}, SP::O6)
> ++                     .Cases({"r15", "o7"}, SP::O7)
> ++                     .Cases({"r16", "l0"}, SP::L0)
> ++                     .Cases({"r17", "l1"}, SP::L1)
> ++                     .Cases({"r18", "l2"}, SP::L2)
> ++                     .Cases({"r19", "l3"}, SP::L3)
> ++                     .Cases({"r20", "l4"}, SP::L4)
> ++                     .Cases({"r21", "l5"}, SP::L5)
> ++                     .Cases({"r22", "l6"}, SP::L6)
> ++                     .Cases({"r23", "l7"}, SP::L7)
> ++                     .Cases({"r0", "g0"}, SP::G0)
> ++                     .Cases({"r1", "g1"}, SP::G1)
> ++                     .Cases({"r2", "g2"}, SP::G2)
> ++                     .Cases({"r3", "g3"}, SP::G3)
> ++                     .Cases({"r4", "g4"}, SP::G4)
> ++                     .Cases({"r5", "g5"}, SP::G5)
> ++                     .Cases({"r6", "g6"}, SP::G6)
> ++                     .Cases({"r7", "g7"}, SP::G7)
> ++                     .Default(0);
> + 
> +   // If we're directly referencing register names
> +   // (e.g in GCC C extension `register int r asm("g1");`),


-- 
Regards,
Robert Nagy