Index | Thread | Search

From:
Visa Hankala <visa@hankala.org>
Subject:
Re: Switch octeon to LLD
To:
"Kirill A. Korinsky" <kirill@korins.ky>
Cc:
tech@openbsd.org
Date:
Thu, 29 Oct 2026 17:07:01 +0000

Download raw body.

Thread
On Thu, Oct 29, 2026 at 02:49:04PM +0100, Kirill A. Korinsky wrote:
> tech@,
> 
> I've tried to switch octeon in -current to LLD as simple as:
> 
> Index: share/mk/bsd.own.mk
> ===================================================================
> RCS file: /cvs/src/share/mk/bsd.own.mk,v
> retrieving revision 1.217
> diff -u -p -r1.217 bsd.own.mk
> --- share/mk/bsd.own.mk 20 Aug 2026 21:23:47 -0000      1.217
> +++ share/mk/bsd.own.mk 19 Oct 2026 10:52:15 -0000
> @@ -17,7 +17,7 @@ YP?=          yes
>  
>  CLANG_ARCH=aarch64 amd64 arm i386 mips64 mips64el powerpc powerpc64 riscv64 sparc64
>  GCC4_ARCH=alpha hppa m88k sh
> -LLD_ARCH=aarch64 amd64 arm i386 powerpc powerpc64 riscv64 sparc64
> +LLD_ARCH=aarch64 amd64 arm i386 mips64 powerpc powerpc64 riscv64 sparc64
>  LLDB_ARCH=aarch64 amd64
>  
>  # Can't use ${CLANG_ARCH} ${GCC4_ARCH} below because of sparc64
> 
> and it works well to build whole base and kernel.
> 
> No additional work is required for LLD at least for this.
> 
> The only things which I needed is a bit tricky first build sequence:
> 
> 1. Rebuild clang / lld as system compiler
> 
> cd /usr/src/share/mk
> doas make install
> cd /usr/src/gnu/usr.bin/clang
> doas make clean
> doas -u build make CC=clang CXX=clang++ LD=ld.lld -jXX
> doas make install
> 
> 2. Build and install temporary libraries
> cd /usr/src/lib
> doas make clean 
> doas -u build make HOSTLDFLAGS=-static -jXX
> doas make NOMAN=1 install
> doas /sbin/ldconfig -R
> 
> 3. Rebuild the system itself
> cd /usr/src
> doas make build -jXX
> doas make install
> 
> But, here the ports collection.
> 
> I have no means to run bulk build for all of them and I'm looking for anyone
> who willing to help me to run it on octeons. If anyone can share access to
> large octeon machine, I can do it myself.
> 
> Obviusly I'm volunteering to fix all discovered LLVM/LLD issues during that bulk.
> 
> To switch ports we need something like that:
> 
> Index: infrastructure/mk/arch-defines.mk
> ===================================================================
> RCS file: /cvs/ports/infrastructure/mk/arch-defines.mk,v
> retrieving revision 1.120
> diff -u -p -r1.120 arch-defines.mk
> --- infrastructure/mk/arch-defines.mk   23 Sep 2026 13:45:41 -0000      1.120
> +++ infrastructure/mk/arch-defines.mk   19 Oct 2026 10:55:08 -0000
> @@ -34,7 +34,7 @@ OCAML_NATIVE_DYNLINK_ARCHS = aarch64 amd
>  # arches where the base compiler is clang
>  CLANG_ARCHS = aarch64 amd64 arm i386 mips64 mips64el powerpc powerpc64 riscv64 sparc64
>  # arches using LLVM's linker (ld.lld); others use binutils' ld.bfd
> -LLD_ARCHS = aarch64 amd64 arm i386 powerpc powerpc64 riscv64 sparc64
> +LLD_ARCHS = aarch64 amd64 arm i386 mips64 powerpc powerpc64 riscv64 sparc64
>  
>  # arches where ports devel/llvm builds - populates llvm ONLY_FOR_ARCHS
>  # as well as available for PROPERTIES checks.
> 
> 
> Thoughts?

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.

Index: gnu/llvm/lld/ELF/Config.h
===================================================================
RCS file: src/gnu/llvm/lld/ELF/Config.h,v
retrieving revision 1.7
diff -u -p -r1.7 Config.h
--- gnu/llvm/lld/ELF/Config.h	29 May 2026 11:06:20 -0000	1.7
+++ gnu/llvm/lld/ELF/Config.h	29 Oct 2026 16:42:16 -0000
@@ -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;
Index: gnu/llvm/lld/ELF/Relocations.cpp
===================================================================
RCS file: src/gnu/llvm/lld/ELF/Relocations.cpp,v
retrieving revision 1.12
diff -u -p -r1.12 Relocations.cpp
--- gnu/llvm/lld/ELF/Relocations.cpp	30 Jul 2026 19:28:37 -0000	1.12
+++ gnu/llvm/lld/ELF/Relocations.cpp	29 Oct 2026 16:42:16 -0000
@@ -1442,6 +1442,8 @@ void RelocScan::scanEhSection(EhInputSec
     if (sym->isUndefined() &&
         maybeReportUndefined(cast<Undefined>(*sym), offset))
       continue;
+    if (ctx.arg.emachine == EM_MIPS)
+      sym->setFlags(HAS_MIPS_NONCALL_RELOC);
     process(r.expr, r.type, offset, *sym, r.addend);
   }
 }
Index: gnu/llvm/lld/ELF/Symbols.h
===================================================================
RCS file: src/gnu/llvm/lld/ELF/Symbols.h,v
retrieving revision 1.9
diff -u -p -r1.9 Symbols.h
--- gnu/llvm/lld/ELF/Symbols.h	30 Jul 2026 19:28:37 -0000	1.9
+++ gnu/llvm/lld/ELF/Symbols.h	29 Oct 2026 16:42:16 -0000
@@ -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,
+  HAS_MIPS_NONCALL_RELOC = 1 << 14,
 };
 
 // The base class for real symbol classes.
Index: gnu/llvm/lld/ELF/SyntheticSections.cpp
===================================================================
RCS file: src/gnu/llvm/lld/ELF/SyntheticSections.cpp,v
retrieving revision 1.11
diff -u -p -r1.11 SyntheticSections.cpp
--- gnu/llvm/lld/ELF/SyntheticSections.cpp	30 Jul 2026 19:28:37 -0000	1.11
+++ gnu/llvm/lld/ELF/SyntheticSections.cpp	29 Oct 2026 16:42:16 -0000
@@ -1177,13 +1177,21 @@ void MipsGotSection::writeTo(uint8_t *bu
     // 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 +1210,49 @@ void MipsGotSection::writeTo(uint8_t *bu
   }
 }
 
+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(HAS_MIPS_NONCALL_RELOC) || sym->hasFlag(NEEDS_PLT))
+      continue;
+    if (!sym->isInGot(ctx))
+      continue;
+    indices[sym] = entries.size();
+    entries.push_back(sym);
+  }
+}
+
+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() {
+  stubSize = ctx.mainPart->dynSymTab->getNumSymbols() > 0x10000
+                 ? stubBigSize
+                 : stubNormalSize;
+}
+
+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 +2402,9 @@ template <class ELFT> void SymbolTableSe
       Symbol *sym = ent.sym;
       if (sym->isInPlt(ctx) && sym->hasFlag(NEEDS_COPY))
         eSym->st_other |= STO_MIPS_PLT;
+      if (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 +4981,11 @@ template <class ELFT> void elf::createSy
   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);
Index: gnu/llvm/lld/ELF/SyntheticSections.h
===================================================================
RCS file: src/gnu/llvm/lld/ELF/SyntheticSections.h,v
retrieving revision 1.6
diff -u -p -r1.6 SyntheticSections.h
--- gnu/llvm/lld/ELF/SyntheticSections.h	30 Jul 2026 19:28:37 -0000	1.6
+++ gnu/llvm/lld/ELF/SyntheticSections.h	29 Oct 2026 16:42:16 -0000
@@ -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.
Index: gnu/llvm/lld/ELF/Target.h
===================================================================
RCS file: src/gnu/llvm/lld/ELF/Target.h,v
retrieving revision 1.2
diff -u -p -r1.2 Target.h
--- gnu/llvm/lld/ELF/Target.h	30 Jul 2026 19:28:37 -0000	1.2
+++ gnu/llvm/lld/ELF/Target.h	29 Oct 2026 16:42:16 -0000
@@ -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);
 
Index: gnu/llvm/lld/ELF/Writer.cpp
===================================================================
RCS file: src/gnu/llvm/lld/ELF/Writer.cpp,v
retrieving revision 1.12
diff -u -p -r1.12 Writer.cpp
--- gnu/llvm/lld/ELF/Writer.cpp	20 Aug 2026 14:39:18 -0000	1.12
+++ gnu/llvm/lld/ELF/Writer.cpp	29 Oct 2026 16:42:16 -0000
@@ -2018,6 +2018,8 @@ template <class ELFT> void Writer<ELFT>:
 
   if (ctx.in.mipsGot)
     ctx.in.mipsGot->build();
+  if (ctx.in.mipsStubs)
+    ctx.in.mipsStubs->addSymbols();
 
   removeUnusedSyntheticSections(ctx);
   ctx.script->diagnoseOrphanHandling();
@@ -2139,6 +2141,8 @@ template <class ELFT> void Writer<ELFT>:
       finalizeSynthetic(ctx, part.verNeed.get());
       finalizeSynthetic(ctx, part.dynamic.get());
     }
+
+    finalizeSynthetic(ctx, ctx.in.mipsStubs.get());
   }
 
   if (!ctx.script->hasSectionsCommand && !ctx.arg.relocatable)
Index: gnu/llvm/lld/ELF/Arch/Mips.cpp
===================================================================
RCS file: src/gnu/llvm/lld/ELF/Arch/Mips.cpp,v
retrieving revision 1.1.1.6
diff -u -p -r1.1.1.6 Mips.cpp
--- gnu/llvm/lld/ELF/Arch/Mips.cpp	29 May 2026 11:05:55 -0000	1.1.1.6
+++ gnu/llvm/lld/ELF/Arch/Mips.cpp	29 Oct 2026 16:42:16 -0000
@@ -48,6 +48,34 @@ uint64_t elf::getMipsPageAddr(uint64_t a
   return (addr + 0x8000) & ~0xffff;
 }
 
+void elf::writeMipsStub(Ctx &ctx, uint8_t *buf, uint32_t dynsymIndex,
+                        bool big) {
+  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);
+  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, 0x0320f809); // jalr $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, 0x0320f809);  // jalr $25
+    write32(ctx, buf + 12, loadIndex);  // delay slot
+  }
+}
+
 template <class ELFT> MIPS<ELFT>::MIPS(Ctx &ctx) : TargetInfo(ctx) {
   gotPltHeaderEntriesNum = 2;
   defaultMaxPageSize = 65536;
@@ -609,6 +637,13 @@ static RelType getMipsPairType(RelType t
   }
 }
 
+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 +675,21 @@ void MIPS<ELFT>::scanSectionImpl(InputSe
         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(HAS_MIPS_NONCALL_RELOC);
+    }
+
     auto addend = rs.getAddend<ELFT>(rel, type);
     if (expr == RE_MIPS_GOTREL && sym.isLocal()) {
       addend += sec.getFile<ELFT>()->mipsGp0;