Index | Thread | Search

From:
Kirill A. Korinsky <kirill@korins.ky>
Subject:
Re: Switch octeon to LLD
To:
Visa Hankala <visa@hankala.org>
Cc:
tech@openbsd.org
Date:
Fri, 30 Oct 2026 01:45:28 +0100

Download raw body.

Thread
On Thu, 29 Oct 2026 18:07:01 +0100,
Visa Hankala <visa@hankala.org> wrote:
> 
> I have not switched mips64 to ld.lld because the linker is not ready.
> The situation has improved gradually over the years, but ld.lld still
> lacks at least support for lazy binding on MIPS.
> 
> src/regress/libexec/ld.so/lazy shows the lazy binding issue clearly.
> 
> src/regress should be clean from linker-related issues before trying
> ports builds. Doing otherwise is just waste of time.
> 
> Below is an unfinished sketchy diff for the lazy binding support.
>

Thanks for the diff. With releativly small tuning I cooked this one, which
passed whole src/regress/libexec/ld.so

Instead silently identify the wrong symbol by (dynsymIndex >> 16) & 0x7fff,
I had added nice error message.

Also, I had renamed HAS_MIPS_NONCALL_RELOC to NO_MIPS_STUB because the bit
now also marks symbols referenced through a secondary GOT.

Here the resulted diff.

diff --git gnu/llvm/lld/ELF/Arch/Mips.cpp gnu/llvm/lld/ELF/Arch/Mips.cpp
index 091903b103a..936a43a6f23 100644
--- gnu/llvm/lld/ELF/Arch/Mips.cpp
+++ gnu/llvm/lld/ELF/Arch/Mips.cpp
@@ -48,6 +48,41 @@ uint64_t elf::getMipsPageAddr(uint64_t addr) {
   return (addr + 0x8000) & ~0xffff;
 }
 
+void elf::writeMipsStub(Ctx &ctx, uint8_t *buf, uint32_t dynsymIndex,
+                        bool big) {
+  if (dynsymIndex > 0x7fffffff) {
+    ErrAlways(ctx) << "MIPS lazy-binding stub dynamic symbol index "
+                   << dynsymIndex << " exceeds 31 bits";
+    return;
+  }
+
+  const uint32_t load = ctx.arg.is64 ? 0xdf998010  // ld $25, -0x7ff0($28)
+                                     : 0x8f998010; // lw $25, -0x7ff0($28)
+  const uint32_t lui = 0x3c180000 | ((dynsymIndex >> 16) & 0x7fff);
+  const uint32_t jalr = ctx.arg.zHazardplt ? 0x0320fc09 : 0x0320f809;
+  uint32_t loadIndex;
+  if (big)
+    loadIndex = 0x37180000 | (dynsymIndex & 0xffff); // ori $24, $24, idx
+  else if (dynsymIndex & ~0x7fff)
+    loadIndex = 0x34180000 | (dynsymIndex & 0xffff); // ori $24, $0, idx
+  else
+    loadIndex = (ctx.arg.is64 ? 0x64180000 : 0x24180000) | // [d]addiu $24,
+                dynsymIndex;                               //   $0, idx
+
+  if (big) {
+    write32(ctx, buf, load);
+    write32(ctx, buf + 4, 0x03e07825);  // move $15, $31
+    write32(ctx, buf + 8, lui);
+    write32(ctx, buf + 12, jalr);       // jalr[.hb] $25
+    write32(ctx, buf + 16, loadIndex);  // delay slot
+  } else {
+    write32(ctx, buf, load);
+    write32(ctx, buf + 4, 0x03e07825);  // move $15, $31
+    write32(ctx, buf + 8, jalr);        // jalr[.hb] $25
+    write32(ctx, buf + 12, loadIndex);  // delay slot
+  }
+}
+
 template <class ELFT> MIPS<ELFT>::MIPS(Ctx &ctx) : TargetInfo(ctx) {
   gotPltHeaderEntriesNum = 2;
   defaultMaxPageSize = 65536;
@@ -609,6 +644,13 @@ static RelType getMipsPairType(RelType type, bool isLocal) {
   }
 }
 
+static bool isCallReloc(RelType type) {
+  return type == R_MIPS_CALL16 ||
+         type == R_MIPS_CALL_HI16 || type == R_MIPS_CALL_LO16 ||
+         type == R_MICROMIPS_CALL16 ||
+         type == R_MICROMIPS_CALL_HI16 || type == R_MICROMIPS_CALL_LO16;
+}
+
 template <class ELFT>
 template <class RelTy>
 void MIPS<ELFT>::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
@@ -640,6 +682,21 @@ void MIPS<ELFT>::scanSectionImpl(InputSectionBase &sec, Relocs<RelTy> rels) {
         rs.maybeReportUndefined(cast<Undefined>(sym), offset))
       continue;
 
+    // In a shared object, turn untyped undefined symbols with a call
+    // relocation into function symbols so that the dynamic linker handles
+    // them correctly.
+    if (ctx.arg.shared && sym.isUndefined() && symIdx != 0 &&
+        sym.type == STT_NOTYPE && isCallReloc(type.v & 0xff))
+      sym.type = STT_FUNC;
+
+    if (symIdx != 0) {
+      uint32_t baseType = type.v & 0xff;
+      if (isCallReloc(baseType))
+        sym.setFlags(HAS_MIPS_CALL_RELOC);
+      else if (baseType != R_MIPS_JALR && baseType != R_MICROMIPS_JALR)
+        sym.setFlags(NO_MIPS_STUB);
+    }
+
     auto addend = rs.getAddend<ELFT>(rel, type);
     if (expr == RE_MIPS_GOTREL && sym.isLocal()) {
       addend += sec.getFile<ELFT>()->mipsGp0;
diff --git gnu/llvm/lld/ELF/Config.h gnu/llvm/lld/ELF/Config.h
index 5c14a36c398..82add85eae4 100644
--- gnu/llvm/lld/ELF/Config.h
+++ gnu/llvm/lld/ELF/Config.h
@@ -65,6 +65,7 @@ class InputSection;
 class IpltSection;
 class MipsGotSection;
 class MipsRldMapSection;
+class MipsStubsSection;
 class PPC32Got2Section;
 class PPC64LongBranchTargetSection;
 class PltSection;
@@ -592,6 +593,7 @@ struct InStruct {
   std::unique_ptr<SyntheticSection> mipsOptions;
   std::unique_ptr<SyntheticSection> mipsReginfo;
   std::unique_ptr<MipsRldMapSection> mipsRldMap;
+  std::unique_ptr<MipsStubsSection> mipsStubs;
   std::unique_ptr<SyntheticSection> partEnd;
   std::unique_ptr<SyntheticSection> partIndex;
   std::unique_ptr<PltSection> plt;
diff --git gnu/llvm/lld/ELF/Relocations.cpp gnu/llvm/lld/ELF/Relocations.cpp
index a356a60b686..887341a9af7 100644
--- gnu/llvm/lld/ELF/Relocations.cpp
+++ gnu/llvm/lld/ELF/Relocations.cpp
@@ -1442,6 +1442,8 @@ void RelocScan::scanEhSection(EhInputSection &s) {
     if (sym->isUndefined() &&
         maybeReportUndefined(cast<Undefined>(*sym), offset))
       continue;
+    if (ctx.arg.emachine == EM_MIPS)
+      sym->setFlags(NO_MIPS_STUB);
     process(r.expr, r.type, offset, *sym, r.addend);
   }
 }
diff --git gnu/llvm/lld/ELF/Symbols.h gnu/llvm/lld/ELF/Symbols.h
index 1fbb8f1cd82..6e259480bef 100644
--- gnu/llvm/lld/ELF/Symbols.h
+++ gnu/llvm/lld/ELF/Symbols.h
@@ -55,6 +55,8 @@ enum {
   NEEDS_GOT_NONAUTH = 1 << 10,
   NEEDS_TLSDESC_AUTH = 1 << 11,
   NEEDS_TLSDESC_NONAUTH = 1 << 12,
+  HAS_MIPS_CALL_RELOC = 1 << 13,
+  NO_MIPS_STUB = 1 << 14,
 };
 
 // The base class for real symbol classes.
diff --git gnu/llvm/lld/ELF/SyntheticSections.cpp gnu/llvm/lld/ELF/SyntheticSections.cpp
index 2c68b7ef248..f033dc1128e 100644
--- gnu/llvm/lld/ELF/SyntheticSections.cpp
+++ gnu/llvm/lld/ELF/SyntheticSections.cpp
@@ -1095,8 +1095,10 @@ void MipsGotSection::build() {
     if (&got == primGot)
       continue;
 
-    // Dynamic relocations for "global" entries.
+    // A lazy stub requires the primary GOT's resolver slots and $gp.
+    // Calls through secondary GOTs must be resolved eagerly.
     for (const std::pair<Symbol *, size_t> &p : got.global) {
+      p.first->setFlags(NO_MIPS_STUB);
       uint64_t offset = p.second * ctx.arg.wordsize;
       ctx.mainPart->relaDyn->addSymbolReloc(ctx.target->relativeRel, *this,
                                             offset, *p.first);
@@ -1177,13 +1179,21 @@ void MipsGotSection::writeTo(uint8_t *buf) {
     // http://web.archive.org/web/20190324223224/https://www.linux-mips.org/wiki/NPTL
     for (const std::pair<GotEntry, size_t> &p : g.local16)
       write(p.second, p.first.first, p.first.second);
+    auto writeGlobal = [&](size_t i, const Symbol *s) {
+      if (std::optional<uint64_t> va =
+              ctx.in.mipsStubs ? ctx.in.mipsStubs->getStubVA(*s)
+                               : std::nullopt)
+        write(i, nullptr, *va);
+      else
+        write(i, s, 0);
+    };
     // Write VA to the primary GOT only. For secondary GOTs that
     // will be done by REL32 dynamic relocations.
     if (&g == &gots.front())
       for (const std::pair<Symbol *, size_t> &p : g.global)
-        write(p.second, p.first, 0);
+        writeGlobal(p.second, p.first);
     for (const std::pair<Symbol *, size_t> &p : g.relocs)
-      write(p.second, p.first, 0);
+      writeGlobal(p.second, p.first);
     for (const std::pair<Symbol *, size_t> &p : g.tls)
       write(p.second, p.first,
             p.first->isPreemptible || ctx.arg.shared ? 0 : -0x7000);
@@ -1202,6 +1212,54 @@ void MipsGotSection::writeTo(uint8_t *buf) {
   }
 }
 
+MipsStubsSection::MipsStubsSection(Ctx &ctx)
+    : SyntheticSection(ctx, ".MIPS.stubs", SHT_PROGBITS,
+                       SHF_ALLOC | SHF_EXECINSTR, ctx.arg.wordsize) {}
+
+void MipsStubsSection::addSymbols() {
+  // microMIPS stubs are not implemented.
+  if (isMicroMips(ctx))
+    return;
+  for (Symbol *sym : ctx.symtab->getSymbols()) {
+    if (!sym->isPreemptible || !(sym->isUndefined() || sym->isShared()) ||
+        !sym->isFunc() || !sym->hasFlag(HAS_MIPS_CALL_RELOC) ||
+        sym->hasFlag(NO_MIPS_STUB) || sym->hasFlag(NEEDS_PLT))
+      continue;
+    entries.push_back(sym);
+  }
+
+  // Include the full stub size in GOT page counts before GOT layout.
+  // Removing secondary GOT candidates later can only shrink this size.
+  stubSize = ctx.mainPart->dynSymTab->getNumSymbols() > 0x10000
+                 ? stubBigSize
+                 : stubNormalSize;
+}
+
+std::optional<uint64_t>
+MipsStubsSection::getStubVA(const Symbol &sym) const {
+  if (!getParent())
+    return std::nullopt;
+  auto it = indices.find(&sym);
+  if (it == indices.end())
+    return std::nullopt;
+  return getVA() + it->second * stubSize;
+}
+
+void MipsStubsSection::finalizeContents() {
+  llvm::erase_if(entries, [&](const Symbol *sym) {
+    return sym->hasFlag(NO_MIPS_STUB) || !sym->isInGot(ctx);
+  });
+  for (size_t i = 0; i != entries.size(); ++i)
+    indices[entries[i]] = i;
+}
+
+void MipsStubsSection::writeTo(uint8_t *buf) {
+  for (const Symbol *sym : entries) {
+    writeMipsStub(ctx, buf, sym->dynsymIndex, stubSize == stubBigSize);
+    buf += stubSize;
+  }
+}
+
 // On PowerPC the .plt section is used to hold the table of function addresses
 // instead of the .got.plt, and the type is SHT_NOBITS similar to a .bss
 // section. I don't know why we have a BSS style type for the section but it is
@@ -2351,6 +2409,9 @@ template <class ELFT> void SymbolTableSection<ELFT>::writeTo(uint8_t *buf) {
       Symbol *sym = ent.sym;
       if (sym->isInPlt(ctx) && sym->hasFlag(NEEDS_COPY))
         eSym->st_other |= STO_MIPS_PLT;
+      if (type == SHT_DYNSYM && ctx.in.mipsStubs)
+        if (std::optional<uint64_t> va = ctx.in.mipsStubs->getStubVA(*sym))
+          eSym->st_value = *va;
       if (isMicroMips(ctx)) {
         // We already set the less-significant bit for symbols
         // marked by the `STO_MIPS_MICROMIPS` flag and for microMIPS PLT
@@ -4927,6 +4988,11 @@ template <class ELFT> void elf::createSyntheticSections(Ctx &ctx) {
   ctx.in.iplt = std::make_unique<IpltSection>(ctx);
   add(*ctx.in.iplt);
 
+  if (ctx.arg.emachine == EM_MIPS && !ctx.arg.relocatable && ctx.hasDynsym) {
+    ctx.in.mipsStubs = std::make_unique<MipsStubsSection>(ctx);
+    add(*ctx.in.mipsStubs);
+  }
+
   if (ctx.arg.andFeatures || ctx.aarch64PauthAbiCoreInfo) {
     ctx.in.gnuProperty = std::make_unique<GnuPropertySection>(ctx);
     add(*ctx.in.gnuProperty);
diff --git gnu/llvm/lld/ELF/SyntheticSections.h gnu/llvm/lld/ELF/SyntheticSections.h
index 5c7eead69a6..b65422c4af5 100644
--- gnu/llvm/lld/ELF/SyntheticSections.h
+++ gnu/llvm/lld/ELF/SyntheticSections.h
@@ -1202,6 +1202,24 @@ public:
   void writeTo(uint8_t *buf) override {}
 };
 
+class MipsStubsSection final : public SyntheticSection {
+  static constexpr size_t stubNormalSize = 16;
+  static constexpr size_t stubBigSize = 20;
+public:
+  MipsStubsSection(Ctx &);
+  void addSymbols();
+  std::optional<uint64_t> getStubVA(const Symbol &sym) const;
+  size_t getSize() const override { return entries.size() * stubSize; }
+  bool isNeeded() const override { return !entries.empty(); }
+  void finalizeContents() override;
+  void writeTo(uint8_t *buf) override;
+
+private:
+  SmallVector<const Symbol *, 0> entries;
+  llvm::DenseMap<const Symbol *, uint32_t> indices;
+  size_t stubSize = stubNormalSize;
+};
+
 // Representation of the combined .ARM.Exidx input sections. We process these
 // as a SyntheticSection like .eh_frame as we need to merge duplicate entries
 // and add terminating sentinel entries.
diff --git gnu/llvm/lld/ELF/Target.h gnu/llvm/lld/ELF/Target.h
index 437fe9df77a..f8dcaff4829 100644
--- gnu/llvm/lld/ELF/Target.h
+++ gnu/llvm/lld/ELF/Target.h
@@ -247,6 +247,7 @@ uint64_t getMipsPageAddr(uint64_t addr);
 bool isMipsN32Abi(Ctx &, const InputFile &f);
 bool isMicroMips(Ctx &);
 bool isMipsR6(Ctx &);
+void writeMipsStub(Ctx &, uint8_t *buf, uint32_t dynsymIndex, bool big);
 
 void writePPC32GlinkSection(Ctx &, uint8_t *buf, size_t numEntries);
 
diff --git gnu/llvm/lld/ELF/Writer.cpp gnu/llvm/lld/ELF/Writer.cpp
index a5c9d0a6cfe..4b08b9ab4ec 100644
--- gnu/llvm/lld/ELF/Writer.cpp
+++ gnu/llvm/lld/ELF/Writer.cpp
@@ -2016,8 +2016,11 @@ template <class ELFT> void Writer<ELFT>::finalizeSections() {
     }
   }
 
+  if (ctx.in.mipsStubs)
+    ctx.in.mipsStubs->addSymbols();
   if (ctx.in.mipsGot)
     ctx.in.mipsGot->build();
+  finalizeSynthetic(ctx, ctx.in.mipsStubs.get());
 
   removeUnusedSyntheticSections(ctx);
   ctx.script->diagnoseOrphanHandling();


-- 
wbr, Kirill