[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