[llvm] [BOLT][RISCV] Implement indirect PLT calls (PR #219184)

via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 27 04:45:37 PDT 2026


llvmorg-github-actions[bot] wrote:


<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-backend-risc-v

@llvm/pr-subscribers-bolt

Author: Thrrreeee (Thrrreeee)

<details>
<summary>Changes</summary>

This patch implements `MCPlusBuilder::createIndirectPLTCall` for RISC-V, enabling BOLT's `--plt=hot` and `--plt=all` optimizations for RISC-V binaries.

The PLT call pass replaces direct calls and tail calls to PLT entries with indirect calls through the corresponding resolved GOT slot. The generated sequence is:

    auipc  t3, %pcrel_hi(target@<!-- -->GOT)
    l[dw]  t3, %pcrel_lo(.Lpcrel_hi)(t3)
    jalr   ra, t3, 0

---
Full diff: https://github.com/llvm/llvm-project/pull/219184.diff


2 Files Affected:

- (modified) bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp (+53) 
- (added) bolt/test/RISCV/plt-call.test (+44) 


``````````diff
diff --git a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
index 2388505ffd8ed..4c4b934b92d48 100644
--- a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
+++ b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
@@ -306,6 +306,59 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
     return createCall(RISCV::PseudoTAIL, Inst, Target, Ctx);
   }
 
+  InstructionListType createIndirectPLTCall(MCInst &&DirectCall,
+                                            const MCSymbol *TargetLocation,
+                                            MCContext *Ctx) override {
+    const bool IsTailCall = isTailCall(DirectCall);
+    assert(((DirectCall.getOpcode() == RISCV::PseudoCALL && !IsTailCall) ||
+            (DirectCall.getOpcode() == RISCV::PseudoTAIL && IsTailCall)) &&
+           "RISC-V direct (tail) call instruction expected");
+
+    // Load the resolved function address directly from its GOT slot:
+    //
+    //   auipc t3, %pcrel_hi(TargetLocation)
+    //   l[dw] t3, %pcrel_lo(.Lpcrel_hi)(t3)
+    //   jalr  ra, t3, 0
+    //
+    // A tail call uses zero instead of ra as the JALR destination.
+    InstructionListType Code;
+    // Use t3 (x28), the scratch register used by linker-generated RISC-V
+    // PLT/IPLT entries. It is caller-saved, is not an argument register, and
+    // the original call through the PLT already clobbers it.
+    const MCPhysReg PLTScratchReg = RISCV::X28;
+    MCSymbol *AUIPCLabel = Ctx->createNamedTempSymbol("pcrel_hi");
+
+    MCInst InstAUIPC =
+        MCInstBuilder(RISCV::AUIPC).addReg(PLTScratchReg).addImm(0);
+    // TargetLocation is already registered at the existing GOT slot, so use a
+    // direct PC-relative relocation to that slot instead of R_RISCV_GOT_HI20,
+    // which is used when starting from the referenced function symbol.
+    setOperandToSymbolRef(InstAUIPC, /*OpNum=*/1, TargetLocation,
+                          /*Addend=*/0, Ctx, ELF::R_RISCV_PCREL_HI20);
+    setInstLabel(InstAUIPC, AUIPCLabel);
+    Code.emplace_back(std::move(InstAUIPC));
+
+    // Load the call target from the GOT slot using LD on RV64 or LW on RV32.
+    MCInst InstLoad = MCInstBuilder(loadOpc())
+                          .addReg(PLTScratchReg)
+                          .addReg(PLTScratchReg)
+                          .addImm(0);
+    // Pair the I-type LD/LW immediate with the label on AUIPC. RISC-V
+    // R_RISCV_PCREL_LO12_I relocations name the corresponding HI20 location.
+    setOperandToSymbolRef(InstLoad, /*OpNum=*/2, AUIPCLabel,
+                          /*Addend=*/0, Ctx, ELF::R_RISCV_PCREL_LO12_I);
+    Code.emplace_back(std::move(InstLoad));
+
+    MCInst InstCall = MCInstBuilder(RISCV::JALR)
+                          .addReg(IsTailCall ? RISCV::X0 : RISCV::X1)
+                          .addReg(PLTScratchReg)
+                          .addImm(0);
+    moveAnnotations(std::move(DirectCall), InstCall);
+    Code.emplace_back(std::move(InstCall));
+
+    return Code;
+  }
+
   bool analyzeBranch(InstructionIterator Begin, InstructionIterator End,
                      const MCSymbol *&TBB, const MCSymbol *&FBB,
                      MCInst *&CondBranch,
diff --git a/bolt/test/RISCV/plt-call.test b/bolt/test/RISCV/plt-call.test
new file mode 100644
index 0000000000000..76f850e892e59
--- /dev/null
+++ b/bolt/test/RISCV/plt-call.test
@@ -0,0 +1,44 @@
+// Verify that PLTCall optimization works on RISC-V.
+
+// RUN: split-file %s %t.dir
+// RUN: llvm-mc -triple=riscv64 -filetype=obj -o %t.dir/main.o %t.dir/main.s
+// RUN: llvm-mc -triple=riscv64 -filetype=obj -o %t.dir/lib.o %t.dir/lib.s
+// RUN: ld.lld -shared -soname libplt.so -o %t.dir/libplt.so %t.dir/lib.o
+// RUN: ld.lld --no-pie --emit-relocs -z now \
+// RUN:   -dynamic-linker /lib/ld.so.1 %t.dir/main.o %t.dir/libplt.so \
+// RUN:   -o %t.exe
+// RUN: llvm-bolt %t.exe -o %t.bolt --plt=all --print-plt \
+// RUN:   --print-only=_start | FileCheck %s
+
+// Call to foo.
+// CHECK:      auipc t3, %pcrel_hi(foo at GOT)
+// CHECK-NEXT: ld t3, %pcrel_lo({{.*}})(t3)
+// CHECK-NEXT: jalr t3 # PLTCall: 1
+
+// Tail call to bar.
+// CHECK:      auipc t3, %pcrel_hi(bar at GOT)
+// CHECK-NEXT: ld t3, %pcrel_lo({{.*}})(t3)
+// CHECK-NEXT: jr t3 # TAILCALL # PLTCall: 1
+
+//--- main.s
+  .text
+  .globl _start
+  .type _start, @function
+_start:
+  call foo
+  tail bar
+  .size _start, .-_start
+
+//--- lib.s
+  .text
+  .globl foo
+  .type foo, @function
+foo:
+  ret
+  .size foo, .-foo
+
+  .globl bar
+  .type bar, @function
+bar:
+  ret
+  .size bar, .-bar

``````````

</details>


https://github.com/llvm/llvm-project/pull/219184


More information about the llvm-commits mailing list