[llvm] [BOLT][RISCV] Handle static IFUNC calls through .iplt (PR #207733)
via llvm-commits
llvm-commits at lists.llvm.org
Tue Aug 11 05:02:06 PDT 2026
https://github.com/Thrrreeee updated https://github.com/llvm/llvm-project/pull/207733
>From 7f8b8ee0f559a6dcae1fd89d715d582cc96f98b2 Mon Sep 17 00:00:00 2001
From: Thrrreeeee <1379998393 at qq.com>
Date: Tue, 11 Aug 2026 19:44:27 +0800
Subject: [PATCH] [BOLT][RISCV] Handle static IFUNC calls through .iplt
---
bolt/include/bolt/Rewrite/RewriteInstance.h | 9 ++-
bolt/lib/Core/AddressMap.cpp | 19 +++---
bolt/lib/Core/Relocation.cpp | 6 +-
bolt/lib/Rewrite/RewriteInstance.cpp | 70 ++++++++++++++++++---
bolt/test/RISCV/ifunc.s | 54 ++++++++++++++++
5 files changed, 139 insertions(+), 19 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..b58658769b7b0 100644
--- a/bolt/include/bolt/Rewrite/RewriteInstance.h
+++ b/bolt/include/bolt/Rewrite/RewriteInstance.h
@@ -304,8 +304,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. If \p OnlyRISCVIPLT is
+ /// true, process only RISC-V .iplt sections; otherwise skip those sections.
+ 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
@@ -564,7 +565,8 @@ class RewriteInstance {
{".plt"}, {".plt.got"}, {".iplt"}, {nullptr}};
/// RISCV PLT sections.
- const PLTSectionInfo RISCV_PLTSections[2] = {{".plt"}, {nullptr}};
+ const PLTSectionInfo RISCV_PLTSections[3] = {
+ {".plt"}, {".iplt", 16}, {nullptr}};
/// Return PLT information for a section with \p SectionName or nullptr
/// if the section is not PLT.
@@ -579,6 +581,7 @@ class RewriteInstance {
case Triple::aarch64:
PLTSI = AArch64_PLTSections;
break;
+ case Triple::riscv32:
case Triple::riscv64:
PLTSI = RISCV_PLTSections;
break;
diff --git a/bolt/lib/Core/AddressMap.cpp b/bolt/lib/Core/AddressMap.cpp
index f061fea494394..507fdc6f913c7 100644
--- a/bolt/lib/Core/AddressMap.cpp
+++ b/bolt/lib/Core/AddressMap.cpp
@@ -18,16 +18,20 @@ namespace bolt {
const char *const AddressMap::AddressSectionName = ".bolt.addr2addr_map";
const char *const AddressMap::LabelSectionName = ".bolt.label2addr_map";
+// Label keys are host pointers, so map entries use fixed-width 64-bit fields
+// even when processing a 32-bit target.
+static constexpr unsigned MapEntryValueSize = sizeof(uint64_t);
static void emitAddress(MCStreamer &Streamer, uint64_t InputAddress,
const MCSymbol *OutputLabel) {
- Streamer.emitIntValue(InputAddress, 8);
- Streamer.emitSymbolValue(OutputLabel, 8);
+ Streamer.emitIntValue(InputAddress, MapEntryValueSize);
+ Streamer.emitSymbolValue(OutputLabel, MapEntryValueSize);
}
static void emitLabel(MCStreamer &Streamer, const MCSymbol *OutputLabel) {
- Streamer.emitIntValue(reinterpret_cast<uint64_t>(OutputLabel), 8);
- Streamer.emitSymbolValue(OutputLabel, 8);
+ Streamer.emitIntValue(reinterpret_cast<uint64_t>(OutputLabel),
+ MapEntryValueSize);
+ Streamer.emitSymbolValue(OutputLabel, MapEntryValueSize);
}
void AddressMap::emit(MCStreamer &Streamer, BinaryContext &BC) {
@@ -70,8 +74,7 @@ std::optional<AddressMap> AddressMap::parse(BinaryContext &BC) {
AddressMap Parsed;
- unsigned CodePointerSize = BC.AsmInfo->getCodePointerSize();
- const size_t EntrySize = 2 * CodePointerSize;
+ const size_t EntrySize = 2 * MapEntryValueSize;
auto parseSection =
[&](BinarySection &Section,
function_ref<void(uint64_t, uint64_t)> InsertCallback) {
@@ -82,8 +85,8 @@ std::optional<AddressMap> AddressMap::parse(BinaryContext &BC) {
DataExtractor::Cursor Cursor(0);
while (Cursor && !DE.eof(Cursor)) {
- const uint64_t Input = DE.getUnsigned(Cursor, CodePointerSize);
- const uint64_t Output = DE.getUnsigned(Cursor, CodePointerSize);
+ const uint64_t Input = DE.getUnsigned(Cursor, MapEntryValueSize);
+ const uint64_t Output = DE.getUnsigned(Cursor, MapEntryValueSize);
InsertCallback(Input, Output);
}
diff --git a/bolt/lib/Core/Relocation.cpp b/bolt/lib/Core/Relocation.cpp
index 6663abcffc7e8..74f60e007ab4c 100644
--- a/bolt/lib/Core/Relocation.cpp
+++ b/bolt/lib/Core/Relocation.cpp
@@ -128,6 +128,7 @@ static bool isSupportedRISCV(uint32_t Type) {
case ELF::R_RISCV_TPREL_ADD:
case ELF::R_RISCV_TPREL_LO12_I:
case ELF::R_RISCV_TPREL_LO12_S:
+ case ELF::R_RISCV_IRELATIVE:
case ELFReserved::R_RISCV_TPREL_I:
case ELFReserved::R_RISCV_TPREL_S:
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:
+ // R_RISCV_IRELATIVE operates on a wordclass field.
+ return Relocation::Arch == Triple::riscv64 ? 8 : 4;
}
}
@@ -859,7 +863,7 @@ bool Relocation::isIRelative(uint32_t Type) {
return Type == ELF::R_AARCH64_IRELATIVE;
case Triple::riscv64:
case Triple::riscv32:
- llvm_unreachable("not implemented");
+ return Type == ELF::R_RISCV_IRELATIVE;
case Triple::x86_64:
return Type == ELF::R_X86_64_IRELATIVE;
}
diff --git a/bolt/lib/Rewrite/RewriteInstance.cpp b/bolt/lib/Rewrite/RewriteInstance.cpp
index 05ea606bdad7b..cac8b6566852f 100644
--- a/bolt/lib/Rewrite/RewriteInstance.cpp
+++ b/bolt/lib/Rewrite/RewriteInstance.cpp
@@ -1347,7 +1347,8 @@ void RewriteInstance::discoverFileObjects() {
// that is a subject to dynamic relocation processing.
processDynamicRelocations();
- // Process PLT section.
+ // Process PLT sections. RISC-V .iplt is handled after function boundaries
+ // and resolver secondary entry points have been established.
disassemblePLT();
// See if we missed any functions marked by FDE.
@@ -1385,6 +1386,32 @@ void RewriteInstance::discoverFileObjects() {
adjustFunctionBoundaries(MarkerSymbols);
splitUnmarkedTailFunctions(MarkerSymbols);
+ // R_RISCV_IRELATIVE addends name resolver entry points. LLD may
+ // canonicalize the only IFUNC symbol to the IPLT entry, leaving the resolver
+ // without a symbol. Function sizes are not final when dynamic relocations
+ // are first read, so record these secondary entries after boundary
+ // adjustment.
+ 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());
+ }
+ }
+
+ disassemblePLT(/*OnlyRISCVIPLT=*/true);
+ }
+
// Annotate functions with code/data markers in AArch64
for (auto &[Address, Type] : MarkerSymbols) {
auto *BF = BC->getBinaryFunctionContainingAddress(Address,
@@ -1876,11 +1903,13 @@ 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
BinaryFunction *TargetBF = BC->getBinaryFunctionAtAddress(Rel->Addend);
+ if (!TargetBF)
+ TargetBF = BC->getBinaryFunctionContainingAddress(Rel->Addend);
if (!TargetBF) {
BC->errs()
<< "BOLT-WARNING: Expected BF to be presented as IFUNC resolver at "
@@ -1888,7 +1917,9 @@ void RewriteInstance::createPLTBinaryFunction(uint64_t TargetAddress,
return;
}
- Symbol = TargetBF->getSymbol();
+ const uint64_t ResolverOffset = Rel->Addend - TargetBF->getAddress();
+ Symbol = ResolverOffset ? TargetBF->addEntryPointAtOffset(ResolverOffset)
+ : TargetBF->getSymbol();
}
ErrorOr<BinarySection &> Section = BC->getSectionForAddress(EntryAddress);
@@ -1900,6 +1931,23 @@ void RewriteInstance::createPLTBinaryFunction(uint64_t TargetAddress,
else
BF->addAlternativeName(Symbol->getName().str() + "@PLT");
setPLTSymbol(BF, Symbol->getName());
+
+ if (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 +2036,9 @@ void RewriteInstance::disassemblePLTSectionRISCV(BinarySection &Section) {
}
};
- // Skip the first special entry since no relocation points to it.
- uint64_t InstrOffset = 32;
+ // Regular .plt has a first special entry with no relocations pointing to it,
+ // while static IFUNC .iplt entries start at the beginning of the section.
+ uint64_t InstrOffset = Section.getName() == ".iplt" ? 0 : 32;
while (InstrOffset < SectionSize) {
InstructionListType Instructions;
@@ -2051,7 +2100,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);
@@ -2067,6 +2116,10 @@ void RewriteInstance::disassemblePLT() {
if (!PLTSI)
continue;
+ const bool IsRISCVIPLT = BC->isRISCV() && Section.getName() == ".iplt";
+ if (IsRISCVIPLT != OnlyRISCVIPLT)
+ continue;
+
analyzeOnePLTSection(Section, PLTSI->EntrySize);
BinaryFunction *PltBF;
@@ -2774,7 +2827,10 @@ 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) &&
+ 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..43b26324dfb5c
--- /dev/null
+++ b/bolt/test/RISCV/ifunc.s
@@ -0,0 +1,54 @@
+## 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=IPLT
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32 -mattr=+relax -o %t.32.o %s
+# RUN: ld.lld -q -o %t.32 %t.32.o
+# RUN: llvm-bolt %t.32 -o %t.32.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.32.bolt | FileCheck %s --check-prefix=ELF
+# RUN: llvm-objdump -d --no-show-raw-insn %t.32.bolt \
+# RUN: | FileCheck %s --check-prefix=IPLT
+
+# 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
+
+# IPLT: Disassembly of section .iplt:
+# IPLT: <ifunc0>:
+# IPLT-NEXT: {{.*}} auipc t3,
+# IPLT-NEXT: {{.*}} l{{[dw]}} 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