[llvm] [BOLT][RISCV] Handle static IFUNC calls through .iplt (PR #207733)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 25 23:56:24 PDT 2026
https://github.com/Thrrreeee updated https://github.com/llvm/llvm-project/pull/207733
>From cd9b0d57faa46fabb1824b0b2a78656dd2818734 Mon Sep 17 00:00:00 2001
From: shijinrui <shijinrui at bytedance.com>
Date: Wed, 26 Aug 2026 14:28:10 +0800
Subject: [PATCH] [BOLT][RISCV] Handle static IFUNC calls through .iplt
---
bolt/include/bolt/Rewrite/RewriteInstance.h | 8 +-
bolt/lib/Core/Relocation.cpp | 1 +
bolt/lib/Rewrite/RewriteInstance.cpp | 133 ++++++++++++++++++--
bolt/test/RISCV/ifunc.s | 51 ++++++++
4 files changed, 179 insertions(+), 14 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 8299cc392eade..ffc74adc5f4b8 100644
--- a/bolt/include/bolt/Rewrite/RewriteInstance.h
+++ b/bolt/include/bolt/Rewrite/RewriteInstance.h
@@ -309,8 +309,9 @@ class RewriteInstance {
/// Write .eh_frame_hdr.
void writeEHFrameHeader();
- /// Disassemble and create function entries for PLT.
- void disassemblePLT();
+ /// Disassemble and create function entries for PLT. Process only the deferred
+ /// RISC-V .iplt section when \p OnlyRISCVIPLT is true; otherwise skip it.
+ void disassemblePLT(bool OnlyRISCVIPLT = false);
/// Auxiliary function to create .plt BinaryFunction on \p EntryAddres
/// with the \p EntrySize size. \p TargetAddress is the .got entry
@@ -585,7 +586,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.
diff --git a/bolt/lib/Core/Relocation.cpp b/bolt/lib/Core/Relocation.cpp
index cb6aaa552d05f..502f448f50c18 100644
--- a/bolt/lib/Core/Relocation.cpp
+++ b/bolt/lib/Core/Relocation.cpp
@@ -870,6 +870,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 4486efe926d01..528243a2fd97a 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -1353,7 +1353,18 @@ void RewriteInstance::discoverFileObjects() {
// that is a subject to dynamic relocation processing.
processDynamicRelocations();
- // Process PLT section.
+ SmallVector<uint64_t, 1> RISCVIFUNCResolvers;
+ if (BC->TheTriple->isRISCV64()) {
+ for (const BinarySection &Section : BC->allocatableSections())
+ for (const Relocation &Rel : Section.dynamicRelocations())
+ if (Rel.isIRelative() && Rel.Addend)
+ RISCVIFUNCResolvers.push_back(Rel.Addend);
+ }
+
+ // PLT BinaryFunctions are created with size zero and finalized by
+ // adjustFunctionBoundaries() below. In particular, AArch64 BTI later
+ // disassembles PLT functions and requires their finalized sizes. Defer only
+ // RISC-V .iplt until resolver functions and secondary entries are known.
disassemblePLT();
// See if we missed any functions marked by FDE.
@@ -1385,12 +1396,64 @@ void RewriteInstance::discoverFileObjects() {
FDE->getAddressRange());
}
+ // A stripped resolver with an explicit size on the preceding function is
+ // not covered by any BinaryFunction. Register it before boundary adjustment
+ // so its size can be inferred in the usual way. If the preceding function
+ // has no size, defer to boundary adjustment and keep the resolver as its
+ // secondary entry point.
+ for (const uint64_t Address : RISCVIFUNCResolvers) {
+ if (BC->getBinaryFunctionAtAddress(Address) ||
+ BC->getBinaryFunctionContainingAddress(Address))
+ continue;
+
+ ErrorOr<BinarySection &> Section = BC->getSectionForAddress(Address);
+ if (!Section || !Section->isText() || Section->isVirtual())
+ continue;
+
+ auto BFI = BC->getBinaryFunctions().upper_bound(Address);
+ if (BFI != BC->getBinaryFunctions().begin()) {
+ const BinaryFunction &PreviousBF = std::prev(BFI)->second;
+ if (!PreviousBF.getSize() && PreviousBF.getOriginSection() == &*Section)
+ continue;
+ }
+
+ const std::string FunctionName =
+ "__BOLT_IFUNC_RESOLVERat" + Twine::utohexstr(Address).str();
+ BC->createBinaryFunction(FunctionName, *Section, Address, 0);
+ }
+
BC->setHasSymbolsWithFileName(FileSymbols.size());
// Now that all the functions were created - adjust their boundaries.
adjustFunctionBoundaries(MarkerSymbols);
splitUnmarkedTailFunctions(MarkerSymbols);
+ // This is deliberately RISC-V 64-only. LLD may canonicalize the only IFUNC
+ // symbol to the IPLT entry, leaving the resolver identifiable only by an
+ // R_RISCV_IRELATIVE addend. 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->TheTriple->isRISCV64()) {
+ for (const uint64_t Address : RISCVIFUNCResolvers) {
+ BinaryFunction *BF = BC->getBinaryFunctionContainingAddress(Address);
+ if (!BF || BF->getAddress() == Address)
+ continue;
+ if (BF->isInConstantIsland(Address)) {
+ BC->errs() << "BOLT-ERROR: IFUNC resolver at 0x"
+ << Twine::utohexstr(Address)
+ << " is in constant island of function " << *BF << '\n';
+ exit(1);
+ }
+ BF->addEntryPointAtOffset(Address - BF->getAddress());
+ }
+
+ // Reuse the normal PLT path now that every resolver has an exact function
+ // or secondary-entry symbol.
+ disassemblePLT(/*OnlyRISCVIPLT=*/true);
+ }
+
// Annotate functions with code/data markers in AArch64
for (auto &[Address, Type] : MarkerSymbols) {
auto *BF = BC->getBinaryFunctionContainingAddress(Address,
@@ -1886,11 +1949,16 @@ 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. The existing x86 and AArch64 paths
+ // require the resolver to start a BinaryFunction. 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 "
@@ -1899,17 +1967,46 @@ 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);
assert(Section && "cannot get section for address");
- if (!BF)
+ if (!BF) {
BF = BC->createBinaryFunction(Symbol->getName().str() + "@PLT", *Section,
EntryAddress, 0, EntrySize,
Section->getAlignment());
- else
+ if (BC->TheTriple->isRISCV64() && Section->getName() == ".iplt")
+ // A deferred .iplt entry is created after adjustFunctionBoundaries(), so
+ // give getData() a valid range directly.
+ BF->setMaxSize(EntrySize);
+ } 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, EntrySize,
+ Section->getAlignment());
+ }
+ setPLTSymbol(BF, AliasName);
+ }
+ }
}
void RewriteInstance::disassemblePLTInstruction(const BinarySection &Section,
@@ -1998,8 +2095,9 @@ void RewriteInstance::disassemblePLTSectionRISCV(BinarySection &Section) {
}
};
- // Skip the first special entry since no relocation points to it.
- uint64_t InstrOffset = 32;
+ // A regular .plt has a first special entry with no relocations pointing to
+ // it, while all .iplt sections are headerless.
+ uint64_t InstrOffset = Section.getName() == ".iplt" ? 0 : 32;
while (InstrOffset < SectionSize) {
InstructionListType Instructions;
@@ -2061,7 +2159,7 @@ void RewriteInstance::disassemblePLTSectionX86(BinarySection &Section,
}
}
-void RewriteInstance::disassemblePLT() {
+void RewriteInstance::disassemblePLT(bool OnlyRISCVIPLT) {
auto analyzeOnePLTSection = [&](BinarySection &Section, uint64_t EntrySize) {
if (BC->isAArch64())
return disassemblePLTSectionAArch64(Section);
@@ -2077,6 +2175,10 @@ void RewriteInstance::disassemblePLT() {
if (!PLTSI)
continue;
+ const bool IsRISCVIPLT = BC->isRISCV() && Section.getName() == ".iplt";
+ if (OnlyRISCVIPLT != IsRISCVIPLT)
+ continue;
+
analyzeOnePLTSection(Section, PLTSI->EntrySize);
BinaryFunction *PltBF;
@@ -2089,6 +2191,8 @@ void RewriteInstance::disassemblePLT() {
PltBF = BC->createBinaryFunction(
"__BOLT_PSEUDO_" + Section.getName().str(), Section,
Section.getAddress(), 0, PLTSI->EntrySize, Section.getAlignment());
+ if (OnlyRISCVIPLT)
+ PltBF->setMaxSize(PLTSI->EntrySize);
}
PltBF->setPseudo(true);
}
@@ -2769,6 +2873,7 @@ bool RewriteInstance::analyzeRelocation(
};
const bool IsAArch64 = BC->isAArch64();
+ const bool IsRISCV = BC->isRISCV();
const size_t RelSize = Relocation::getSizeForType(RType);
@@ -2797,8 +2902,14 @@ 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) &&
- (IsAArch64 || BC->isRISCV())) {
+ // 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->TheTriple->isRISCV64() && RType == ELF::R_RISCV_CALL_PLT &&
+ ELFSymbolRef(Symbol).getELFType() == ELF::STT_GNU_IFUNC;
+ if ((!SymbolAddress || IsRISCVIFuncPLT) && !IsWeakReference(Symbol) &&
+ (IsAArch64 || IsRISCV)) {
const BinaryData *BD = BC->getPLTBinaryDataByName(SymbolName);
SymbolAddress = BD ? BD->getAddress() : 0;
}
@@ -2858,7 +2969,7 @@ bool RewriteInstance::analyzeRelocation(
if (SkipVerification)
return true;
- if (IsAArch64 || BC->isRISCV())
+ if (IsAArch64 || IsRISCV)
return true;
if (SymbolName == "__hot_start" || SymbolName == "__hot_end")
diff --git a/bolt/test/RISCV/ifunc.s b/bolt/test/RISCV/ifunc.s
new file mode 100644
index 0000000000000..bd638659d65cb
--- /dev/null
+++ b/bolt/test/RISCV/ifunc.s
@@ -0,0 +1,51 @@
+## Check that BOLT recognizes a non-preemptible IFUNC IPLT entry and tracks an
+## otherwise unnamed resolver after the linker canonicalizes the exported IFUNC
+## symbol to the IPLT entry. Function sizes model normal compiler output, while
+## discarding local symbols removes the resolver's remaining name. Moving the
+## synthesized resolver also verifies that the IRELATIVE addend is updated.
+
+# 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-objcopy --discard-all %t.64
+# 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 > %t.64.dump
+# RUN: llvm-objdump -d --no-show-raw-insn %t.64.bolt >> %t.64.dump
+# RUN: FileCheck %s --input-file=%t.64.dump \
+# RUN: --check-prefixes=ELF,IPLT,RESOLVER
+
+# BOLT: Binary Function "_start
+# BOLT: auipc a0, %pcrel_hi(__BOLT_IFUNC_RESOLVERat{{[0-9a-f]+}}@PLT)
+
+# ELF: R_RISCV_IRELATIVE {{ *}}[[#%x,RESOLVER:]]
+# ELF: {{[0-9a-f]+}} 4 FUNC {{.*}} func
+
+# IPLT: Disassembly of section .iplt:
+# IPLT: <ifunc0>:
+# IPLT-NEXT: {{.*}} auipc t3,
+# IPLT-NEXT: {{.*}} ld t3,
+
+# RESOLVER: {{^ *}}[[#%x,RESOLVER]]:{{ *}}ret
+
+ .text
+ .globl _start
+ .type _start, @function
+_start:
+1:
+ auipc a0, %pcrel_hi(ifunc0)
+ addi a0, a0, %pcrel_lo(1b)
+ .size _start, .-_start
+
+ .globl func
+ .type func, @function
+func:
+ ret
+ .size func, .-func
+
+ .globl ifunc0
+ .type ifunc0, @gnu_indirect_function
+ifunc0:
+ ret
+ .size ifunc0, .-ifunc0
More information about the llvm-commits
mailing list