[llvm] [BOLT][RISCV] Handle static IFUNC calls through .iplt (PR #207733)
via llvm-commits
llvm-commits at lists.llvm.org
Wed Aug 19 06:10:22 PDT 2026
https://github.com/Thrrreeee updated https://github.com/llvm/llvm-project/pull/207733
>From a64264916cd15c2c7d6c0101180754cf7c2aea73 Mon Sep 17 00:00:00 2001
From: shijinrui <shijinrui at bytedance.com>
Date: Wed, 19 Aug 2026 21:09:57 +0800
Subject: [PATCH] [BOLT][RISCV] Handle static IFUNC calls through .iplt
---
bolt/include/bolt/Rewrite/RewriteInstance.h | 12 +-
bolt/lib/Core/Relocation.cpp | 5 +
bolt/lib/Rewrite/RewriteInstance.cpp | 122 +++++++++++++++++---
bolt/test/RISCV/ifunc.s | 44 +++++++
4 files changed, 163 insertions(+), 20 deletions(-)
create mode 100644 bolt/test/RISCV/ifunc.s
diff --git a/bolt/include/bolt/Rewrite/RewriteInstance.h b/bolt/include/bolt/Rewrite/RewriteInstance.h
index a624c056ada14..aacc9dc41a359 100644
--- a/bolt/include/bolt/Rewrite/RewriteInstance.h
+++ b/bolt/include/bolt/Rewrite/RewriteInstance.h
@@ -307,6 +307,14 @@ class RewriteInstance {
/// Disassemble and create function entries for PLT.
void disassemblePLT();
+ /// Disassemble and create function entries for a deferred RISC-V .iplt.
+ void disassembleRISCVIPLT();
+
+ /// Mark the function at the start of \p Section as pseudo, creating it with
+ /// \p EntrySize if it was not registered while disassembling PLT entries.
+ void registerPLTSectionPseudoFunction(BinarySection &Section,
+ uint64_t EntrySize);
+
/// Auxiliary function to create .plt BinaryFunction on \p EntryAddres
/// with the \p EntrySize size. \p TargetAddress is the .got entry
/// associated address.
@@ -564,8 +572,8 @@ class RewriteInstance {
{".plt"}, {".plt.got"}, {".iplt"}, {nullptr}};
/// RISCV PLT sections.
- const PLTSectionInfo RISCV_PLTSections[2] = {{".plt"}, {nullptr}};
-
+ const PLTSectionInfo RISCV_PLTSections[3] = {
+ {".plt", 16}, {".iplt", 16}, {nullptr}};
/// Return PLT information for a section with \p SectionName or nullptr
/// if the section is not PLT.
const PLTSectionInfo *getPLTSectionInfo(StringRef SectionName) {
diff --git a/bolt/lib/Core/Relocation.cpp b/bolt/lib/Core/Relocation.cpp
index 6663abcffc7e8..0862ba0e578bc 100644
--- a/bolt/lib/Core/Relocation.cpp
+++ b/bolt/lib/Core/Relocation.cpp
@@ -130,6 +130,7 @@ static bool isSupportedRISCV(uint32_t Type) {
case ELF::R_RISCV_TPREL_LO12_S:
case ELFReserved::R_RISCV_TPREL_I:
case ELFReserved::R_RISCV_TPREL_S:
+ case ELF::R_RISCV_IRELATIVE:
return true;
}
}
@@ -240,6 +241,9 @@ static size_t getSizeForTypeRISCV(uint32_t Type) {
case ELF::R_RISCV_TLS_GD_HI20:
// See extractValueRISCV for why this is necessary.
return 8;
+ case ELF::R_RISCV_IRELATIVE:
+ // Unsupported on RISCV32.
+ return 8;
}
}
@@ -858,6 +862,7 @@ bool Relocation::isIRelative(uint32_t Type) {
case Triple::aarch64:
return Type == ELF::R_AARCH64_IRELATIVE;
case Triple::riscv64:
+ return Type == ELF::R_RISCV_IRELATIVE;
case Triple::riscv32:
llvm_unreachable("not implemented");
case Triple::x86_64:
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index 05ea606bdad7b..facf73a8b8252 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -1385,6 +1385,34 @@ void RewriteInstance::discoverFileObjects() {
adjustFunctionBoundaries(MarkerSymbols);
splitUnmarkedTailFunctions(MarkerSymbols);
+ // This is deliberately RISC-V-only. LLD may canonicalize the only IFUNC
+ // symbol to the IPLT entry, leaving the resolver named only by an
+ // R_RISCV_IRELATIVE addend at a non-zero offset in another function.
+ // Function sizes are not final when dynamic relocations are first read, so
+ // record exact secondary entries after boundary adjustment and only then
+ // process .iplt. x86 and AArch64 .iplt sections were already processed by
+ // disassemblePLT() above and retain their original ordering.
+ if (BC->isRISCV()) {
+ for (const BinarySection &Section : BC->allocatableSections()) {
+ for (const Relocation &Rel : Section.dynamicRelocations()) {
+ if (!Rel.isIRelative() || !Rel.Addend)
+ continue;
+ BinaryFunction *BF = BC->getBinaryFunctionContainingAddress(Rel.Addend);
+ if (!BF || BF->getAddress() == Rel.Addend)
+ continue;
+ if (BF->isInConstantIsland(Rel.Addend)) {
+ BC->errs() << "BOLT-ERROR: IFUNC resolver at 0x"
+ << Twine::utohexstr(Rel.Addend)
+ << " is in constant island of function " << *BF << '\n';
+ exit(1);
+ }
+ BF->addEntryPointAtOffset(Rel.Addend - BF->getAddress());
+ }
+ }
+
+ disassembleRISCVIPLT();
+ }
+
// Annotate functions with code/data markers in AArch64
for (auto &[Address, Type] : MarkerSymbols) {
auto *BF = BC->getBinaryFunctionContainingAddress(Address,
@@ -1876,11 +1904,15 @@ void RewriteInstance::createPLTBinaryFunction(uint64_t TargetAddress,
MCSymbol *Symbol = Rel->Symbol;
if (!Symbol) {
- if (BC->isRISCV() || !Rel->Addend || !Rel->isIRelative())
+ if (!Rel->Addend || !Rel->isIRelative())
return;
- // IFUNC trampoline without symbol
+ // IFUNC trampoline without symbol. For RISC-V, deferred .iplt processing
+ // also permits the exact secondary entry registered above, which
+ // getBinaryFunctionAtAddress() does not return.
BinaryFunction *TargetBF = BC->getBinaryFunctionAtAddress(Rel->Addend);
+ if (!TargetBF && BC->isRISCV())
+ TargetBF = BC->getBinaryFunctionContainingAddress(Rel->Addend);
if (!TargetBF) {
BC->errs()
<< "BOLT-WARNING: Expected BF to be presented as IFUNC resolver at "
@@ -1889,6 +1921,11 @@ void RewriteInstance::createPLTBinaryFunction(uint64_t TargetAddress,
}
Symbol = TargetBF->getSymbol();
+ if (BC->isRISCV()) {
+ const uint64_t ResolverOffset = Rel->Addend - TargetBF->getAddress();
+ if (ResolverOffset)
+ Symbol = TargetBF->addEntryPointAtOffset(ResolverOffset);
+ }
}
ErrorOr<BinarySection &> Section = BC->getSectionForAddress(EntryAddress);
@@ -1900,6 +1937,25 @@ void RewriteInstance::createPLTBinaryFunction(uint64_t TargetAddress,
else
BF->addAlternativeName(Symbol->getName().str() + "@PLT");
setPLTSymbol(BF, Symbol->getName());
+
+ // Keep RISC-V alias recovery local to the new path so the established x86
+ // and AArch64 PLT naming behavior remains unchanged.
+ if (BC->isRISCV() && Rel->isIRelative()) {
+ auto ResolverSyms = FileSymRefs.equal_range(Rel->Addend);
+ for (const SymbolRef &AliasSymbol : llvm::make_second_range(
+ llvm::make_range(ResolverSyms.first, ResolverSyms.second))) {
+ if (ELFSymbolRef(AliasSymbol).getELFType() != ELF::STT_GNU_IFUNC)
+ continue;
+ StringRef AliasName = cantFail(AliasSymbol.getName());
+ const std::string PLTName = AliasName.str() + "@PLT";
+ if (!BC->getBinaryDataByName(PLTName)) {
+ BF->addAlternativeName(PLTName);
+ BC->registerNameAtAddress(PLTName, EntryAddress, 0, EntrySize,
+ Section->getAlignment());
+ }
+ setPLTSymbol(BF, AliasName);
+ }
+ }
}
void RewriteInstance::disassemblePLTInstruction(const BinarySection &Section,
@@ -1988,8 +2044,9 @@ void RewriteInstance::disassemblePLTSectionRISCV(BinarySection &Section) {
}
};
- // Skip the first special entry since no relocation points to it.
- uint64_t InstrOffset = 32;
+ // A regular RISC-V .plt starts with a 32-byte PLT0 header, while a
+ // standalone .iplt contains only 16-byte non-preemptible IFUNC entries.
+ uint64_t InstrOffset = Section.getName() == ".iplt" ? 0 : 32;
while (InstrOffset < SectionSize) {
InstructionListType Instructions;
@@ -2051,6 +2108,35 @@ void RewriteInstance::disassemblePLTSectionX86(BinarySection &Section,
}
}
+void RewriteInstance::registerPLTSectionPseudoFunction(BinarySection &Section,
+ uint64_t EntrySize) {
+ BinaryFunction *PltBF;
+ auto BFIter = BC->getBinaryFunctions().find(Section.getAddress());
+ if (BFIter != BC->getBinaryFunctions().end()) {
+ PltBF = &BFIter->second;
+ } else {
+ // If we did not register any function at the start of the section, then it
+ // must be a general PLT entry. Add a function at the location.
+ PltBF = BC->createBinaryFunction("__BOLT_PSEUDO_" + Section.getName().str(),
+ Section, Section.getAddress(), 0,
+ EntrySize, Section.getAlignment());
+ }
+ PltBF->setPseudo(true);
+}
+
+void RewriteInstance::disassembleRISCVIPLT() {
+ assert(BC->isRISCV() && "expected RISC-V target");
+
+ for (BinarySection &Section : BC->allocatableSections()) {
+ if (Section.getName() != ".iplt")
+ continue;
+ const PLTSectionInfo *PLTSI = getPLTSectionInfo(Section.getName());
+ assert(PLTSI && "missing RISC-V .iplt section information");
+ disassemblePLTSectionRISCV(Section);
+ registerPLTSectionPseudoFunction(Section, PLTSI->EntrySize);
+ }
+}
+
void RewriteInstance::disassemblePLT() {
auto analyzeOnePLTSection = [&](BinarySection &Section, uint64_t EntrySize) {
if (BC->isAArch64())
@@ -2067,20 +2153,14 @@ void RewriteInstance::disassemblePLT() {
if (!PLTSI)
continue;
- analyzeOnePLTSection(Section, PLTSI->EntrySize);
+ // The LLD-generated RISC-V .iplt form handled here may depend on an
+ // unnamed resolver inside another function. x86 and AArch64 .iplt
+ // sections continue through this path.
+ if (BC->isRISCV() && Section.getName() == ".iplt")
+ continue;
- BinaryFunction *PltBF;
- auto BFIter = BC->getBinaryFunctions().find(Section.getAddress());
- if (BFIter != BC->getBinaryFunctions().end()) {
- PltBF = &BFIter->second;
- } else {
- // If we did not register any function at the start of the section,
- // then it must be a general PLT entry. Add a function at the location.
- PltBF = BC->createBinaryFunction(
- "__BOLT_PSEUDO_" + Section.getName().str(), Section,
- Section.getAddress(), 0, PLTSI->EntrySize, Section.getAlignment());
- }
- PltBF->setPseudo(true);
+ analyzeOnePLTSection(Section, PLTSI->EntrySize);
+ registerPLTSectionPseudoFunction(Section, PLTSI->EntrySize);
}
}
@@ -2774,7 +2854,13 @@ bool RewriteInstance::analyzeRelocation(
// Section symbols are marked as ST_Debug.
IsSectionRelocation = (cantFail(Symbol.getType()) == SymbolRef::ST_Debug);
// Check for PLT entry registered with symbol name
- if (!SymbolAddress && !IsWeakReference(Symbol) &&
+ // LLD may give a defined RISC-V IFUNC symbol the .iplt entry address.
+ // R_RISCV_CALL_PLT must still resolve it through the registered @PLT
+ // BinaryData instead of treating that symbol value as a normal function.
+ const bool IsRISCVIFuncPLT =
+ BC->isRISCV() && RType == ELF::R_RISCV_CALL_PLT &&
+ ELFSymbolRef(Symbol).getELFType() == ELF::STT_GNU_IFUNC;
+ if ((!SymbolAddress || IsRISCVIFuncPLT) && !IsWeakReference(Symbol) &&
(IsAArch64 || BC->isRISCV())) {
const BinaryData *BD = BC->getPLTBinaryDataByName(SymbolName);
SymbolAddress = BD ? BD->getAddress() : 0;
diff --git a/bolt/test/RISCV/ifunc.s b/bolt/test/RISCV/ifunc.s
new file mode 100644
index 0000000000000..db1c841119626
--- /dev/null
+++ b/bolt/test/RISCV/ifunc.s
@@ -0,0 +1,44 @@
+## Check that BOLT recognizes a non-preemptible IFUNC IPLT entry and tracks an
+## otherwise unnamed resolver after the linker canonicalizes the IFUNC symbol
+## to the IPLT entry. Moving the containing function also verifies that the
+## IRELATIVE addend is updated to the resolver secondary entry, not the primary
+## function entry.
+
+# RUN: llvm-mc -filetype=obj -triple=riscv64 -mattr=+relax -o %t.64.o %s
+# RUN: ld.lld -q -o %t.64 %t.64.o
+# RUN: llvm-bolt %t.64 -o %t.64.bolt --use-old-text=0 --lite=0 \
+# RUN: --print-disasm --print-only=_start 2>&1 | FileCheck %s \
+# RUN: --check-prefix=BOLT \
+# RUN: --implicit-check-not="Expected BF to be presented as IFUNC resolver"
+# RUN: llvm-readelf -Wr -Ws %t.64.bolt | FileCheck %s --check-prefix=ELF
+# RUN: llvm-objdump -d --no-show-raw-insn %t.64.bolt \
+# RUN: | FileCheck %s --check-prefix=Check
+
+# BOLT: Binary Function "_start
+# BOLT: auipc a0, %pcrel_hi(__ENTRY_func at 0x{{[0-9a-f]+}}@PLT)
+
+# ELF: R_RISCV_IRELATIVE {{ *}}[[#%x,RESOLVER:]]
+# ELF: {{0*}}[[#%x,RESOLVER-4]] 8 FUNC {{.*}} func
+
+# Check: Disassembly of section .iplt:
+# Check: <ifunc0>:
+# Check-NEXT: {{.*}} auipc t3,
+# Check-NEXT: {{.*}} ld t3,
+
+ .text
+ .globl _start
+ .type _start, @function
+_start:
+1:
+ auipc a0, %pcrel_hi(ifunc0)
+ addi a0, a0, %pcrel_lo(1b)
+
+ .globl func
+ .type func, @function
+func:
+ ret
+
+ .globl ifunc0
+ .type ifunc0, @gnu_indirect_function
+ifunc0:
+ ret
More information about the llvm-commits
mailing list