Download raw body.
Switch octeon to LLD
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
Switch octeon to LLD