From: Kirill A. Korinsky Subject: Re: Switch octeon to LLD To: Visa Hankala Cc: tech@openbsd.org Date: Fri, 30 Oct 2026 01:45:28 +0100 On Thu, 29 Oct 2026 18:07:01 +0100, Visa Hankala 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 MIPS::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 template void MIPS::scanSectionImpl(InputSectionBase &sec, Relocs rels) { @@ -640,6 +682,21 @@ void MIPS::scanSectionImpl(InputSectionBase &sec, Relocs rels) { rs.maybeReportUndefined(cast(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(rel, type); if (expr == RE_MIPS_GOTREL && sym.isLocal()) { addend += sec.getFile()->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 mipsOptions; std::unique_ptr mipsReginfo; std::unique_ptr mipsRldMap; + std::unique_ptr mipsStubs; std::unique_ptr partEnd; std::unique_ptr partIndex; std::unique_ptr 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(*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 &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 &p : g.local16) write(p.second, p.first.first, p.first.second); + auto writeGlobal = [&](size_t i, const Symbol *s) { + if (std::optional 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 &p : g.global) - write(p.second, p.first, 0); + writeGlobal(p.second, p.first); for (const std::pair &p : g.relocs) - write(p.second, p.first, 0); + writeGlobal(p.second, p.first); for (const std::pair &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 +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 void SymbolTableSection::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 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 void elf::createSyntheticSections(Ctx &ctx) { ctx.in.iplt = std::make_unique(ctx); add(*ctx.in.iplt); + if (ctx.arg.emachine == EM_MIPS && !ctx.arg.relocatable && ctx.hasDynsym) { + ctx.in.mipsStubs = std::make_unique(ctx); + add(*ctx.in.mipsStubs); + } + if (ctx.arg.andFeatures || ctx.aarch64PauthAbiCoreInfo) { ctx.in.gnuProperty = std::make_unique(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 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 entries; + llvm::DenseMap 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 void Writer::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