[llvm] [BOLT][RISCV] Fix AUIPC/JALR call rewriting (PR #216882)

Alexander Wilson via llvm-commits llvm-commits at lists.llvm.org
Wed Aug 26 06:39:31 PDT 2026


https://github.com/rdtscp updated https://github.com/llvm/llvm-project/pull/216882

>From 7a8dc5a99536d3def585807a7591d5be8f555d6f Mon Sep 17 00:00:00 2001
From: Alexander Wilson <rdtscp at meta.com>
Date: Fri, 21 Aug 2026 07:49:54 -0700
Subject: [PATCH] [BOLT][RISCV] Fix RV64 AUIPC/JALR call rewriting

R_RISCV_CALL and R_RISCV_CALL_PLT cover an AUIPC/JALR pair. Read both instructions and combine their signed high and low immediates so relocated call targets retain the low 12 bits.

Also recover RV64 linker-resolved standard call and tail-call pairs without relocations. Relocation-less recovery remains intentionally RV64-only. Alternate link-register preservation is provided by the merged #218408.

This revision addresses review feedback by building on the merged generic RISC-V symbolizer and GOT handling from #217944 and retaining only call-specific changes here.

This follows the RISC-V Unprivileged ISA sections on AUIPC and JALR:

https://docs.riscv.org/reference/isa/v20260120/unpriv/rv32.html

It also follows the RISC-V ELF psABI procedure-call relocations:

https://riscv-non-isa.github.io/riscv-elf-psabi-doc/#_relocations

Assisted-by: Codex
---
 bolt/include/bolt/Core/BinaryFunction.h      |   5 +
 bolt/include/bolt/Core/MCPlusBuilder.h       |  17 +++
 bolt/lib/Core/Relocation.cpp                 |  10 +-
 bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp |  55 ++++++++-
 bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp  |  48 +++++++-
 bolt/test/RISCV/call-relocation-pair.s       |  78 +++++++++++++
 bolt/test/RISCV/unsymbolized-call-edge.s     | 116 +++++++++++++++++++
 bolt/test/RISCV/unsymbolized-call-entry.s    |  60 ++++++++++
 bolt/test/RISCV/unsymbolized-call.s          |  60 ++++++++++
 9 files changed, 441 insertions(+), 8 deletions(-)
 create mode 100644 bolt/test/RISCV/call-relocation-pair.s
 create mode 100644 bolt/test/RISCV/unsymbolized-call-edge.s
 create mode 100644 bolt/test/RISCV/unsymbolized-call-entry.s
 create mode 100644 bolt/test/RISCV/unsymbolized-call.s

diff --git a/bolt/include/bolt/Core/BinaryFunction.h b/bolt/include/bolt/Core/BinaryFunction.h
index 14d7f9b5b5359..56529108e033c 100644
--- a/bolt/include/bolt/Core/BinaryFunction.h
+++ b/bolt/include/bolt/Core/BinaryFunction.h
@@ -945,6 +945,11 @@ class BinaryFunction {
   /// Returns the raw binary encoding of this function.
   ErrorOr<ArrayRef<uint8_t>> getData() const;
 
+  /// Return true if data embedded in the function starts at \p Offset.
+  bool isDataInCodeAt(uint64_t Offset) const {
+    return getSizeOfDataInCodeAt(Offset) != 0;
+  }
+
   BinaryFunction &updateState(BinaryFunction::State State) {
     CurrentState = State;
     return *this;
diff --git a/bolt/include/bolt/Core/MCPlusBuilder.h b/bolt/include/bolt/Core/MCPlusBuilder.h
index be0d58af14fc4..25a0dde6dba34 100644
--- a/bolt/include/bolt/Core/MCPlusBuilder.h
+++ b/bolt/include/bolt/Core/MCPlusBuilder.h
@@ -913,6 +913,23 @@ class MCPlusBuilder {
     return false;
   }
 
+  /// Return true if \p First and \p Second form an AUIPC/JALR call pair with
+  /// linker-resolved immediates instead of a symbol reference. Such pairs can
+  /// be emitted for intra-section calls and need to be resymbolized before the
+  /// caller is moved.
+  virtual bool isUnsymbolizedRISCVCall(const MCInst &First,
+                                       const MCInst &Second) const {
+    return false;
+  }
+
+  /// Return the byte offset from AUIPC to the target of an unsymbolized
+  /// AUIPC/JALR call pair, including JALR's clearing of target bit zero.
+  virtual int64_t getUnsymbolizedRISCVCallOffset(const MCInst &First,
+                                                 const MCInst &Second) const {
+    llvm_unreachable("not implemented");
+    return 0;
+  }
+
   /// Used to fill the executable space with instructions
   /// that will trap.
   virtual StringRef getTrapFillValue() const {
diff --git a/bolt/lib/Core/Relocation.cpp b/bolt/lib/Core/Relocation.cpp
index cb6aaa552d05f..62ba95511a2c0 100644
--- a/bolt/lib/Core/Relocation.cpp
+++ b/bolt/lib/Core/Relocation.cpp
@@ -229,8 +229,6 @@ static size_t getSizeForTypeRISCV(uint32_t Type) {
   case ELF::R_RISCV_PCREL_LO12_I:
   case ELF::R_RISCV_PCREL_LO12_S:
   case ELF::R_RISCV_32_PCREL:
-  case ELF::R_RISCV_CALL:
-  case ELF::R_RISCV_CALL_PLT:
   case ELF::R_RISCV_ADD32:
   case ELF::R_RISCV_SUB32:
   case ELF::R_RISCV_HI20:
@@ -239,6 +237,8 @@ static size_t getSizeForTypeRISCV(uint32_t Type) {
   case ELF::R_RISCV_32:
     return 4;
   case ELF::R_RISCV_64:
+  case ELF::R_RISCV_CALL:
+  case ELF::R_RISCV_CALL_PLT:
   case ELF::R_RISCV_GOT_HI20:
   case ELF::R_RISCV_TLS_GOT_HI20:
   case ELF::R_RISCV_TLS_GD_HI20:
@@ -502,7 +502,11 @@ static uint64_t extractValueRISCV(uint32_t Type, uint64_t Contents,
     return extractJImmRISCV(Contents);
   case ELF::R_RISCV_CALL:
   case ELF::R_RISCV_CALL_PLT:
-    return extractUImmRISCV(Contents);
+    // The psABI "Relocations" chapter's "Procedure Calls" section defines
+    // R_RISCV_CALL and R_RISCV_CALL_PLT over an AUIPC/JALR pair. Decode both
+    // instructions so the addend includes the low 12 bits carried by JALR.
+    return extractUImmRISCV(Contents & 0xffffffff) +
+           extractIImmRISCV(Contents >> 32);
   case ELF::R_RISCV_BRANCH:
     return extractBImmRISCV(Contents);
   case ELF::R_RISCV_GOT_HI20:
diff --git a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
index 2388505ffd8ed..b56e0d6448833 100644
--- a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
+++ b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
@@ -28,6 +28,34 @@ using namespace bolt;
 
 namespace {
 
+bool isValidUnsymbolizedCallAUIPC(const MCInst &Inst) {
+  if (Inst.getOpcode() != RISCV::AUIPC ||
+      MCPlus::getNumPrimeOperands(Inst) != 2)
+    return false;
+
+  const MCOperand &Destination = Inst.getOperand(0);
+  return Destination.isReg() && Destination.getReg() != RISCV::X0 &&
+         Inst.getOperand(1).isImm();
+}
+
+bool isValidUnsymbolizedCallJALR(const MCInst &Inst) {
+  if (Inst.getOpcode() != RISCV::JALR || MCPlus::getNumPrimeOperands(Inst) != 3)
+    return false;
+
+  return Inst.getOperand(0).isReg() && Inst.getOperand(1).isReg() &&
+         Inst.getOperand(2).isImm();
+}
+
+bool hasSupportedUnsymbolizedCallRegisters(const MCInst &First,
+                                           const MCInst &Second) {
+  const MCRegister Base = First.getOperand(0).getReg();
+  if (Second.getOperand(1).getReg() != Base)
+    return false;
+
+  const MCRegister Link = Second.getOperand(0).getReg();
+  return Link == RISCV::X0 || (Base == RISCV::X1 && Link == RISCV::X1);
+}
+
 class RISCVMCPlusBuilder : public MCPlusBuilder {
   bool isRV64() const { return STI->hasFeature(RISCV::Feature64Bit); }
   unsigned regSize() const { return isRV64() ? 8 : 4; }
@@ -504,7 +532,9 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
   }
 
   bool isCallAuipc(const MCInst &Inst) const {
-    if (Inst.getOpcode() != RISCV::AUIPC)
+    if (Inst.getOpcode() != RISCV::AUIPC ||
+        MCPlus::getNumPrimeOperands(Inst) != 2 || !Inst.getOperand(0).isReg() ||
+        Inst.getOperand(0).getReg() == RISCV::X0)
       return false;
 
     const auto &ImmOp = Inst.getOperand(1);
@@ -525,13 +555,32 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
   }
 
   bool isRISCVCall(const MCInst &First, const MCInst &Second) const override {
-    if (!isCallAuipc(First))
+    if (!isCallAuipc(First) || !isValidUnsymbolizedCallJALR(Second))
       return false;
 
-    assert(Second.getOpcode() == RISCV::JALR);
     return true;
   }
 
+  bool isUnsymbolizedRISCVCall(const MCInst &First,
+                               const MCInst &Second) const override {
+    if (!isValidUnsymbolizedCallAUIPC(First) ||
+        !isValidUnsymbolizedCallJALR(Second))
+      return false;
+
+    return hasSupportedUnsymbolizedCallRegisters(First, Second);
+  }
+
+  int64_t getUnsymbolizedRISCVCallOffset(const MCInst &First,
+                                         const MCInst &Second) const override {
+    // The RV32I "Integer Computational Instructions" section defines AUIPC's
+    // offset as the sign-extended 20-bit U-immediate shifted left by 12. The
+    // "Control Transfer Instructions" section defines JALR as adding its
+    // sign-extended 12-bit I-immediate and clearing target bit zero. The
+    // symbolic AUIPC decoder passes the sign-extended, shifted value directly.
+    const int64_t Hi = First.getOperand(1).getImm();
+    return (Hi + Second.getOperand(2).getImm()) & ~1LL;
+  }
+
   uint16_t getMinFunctionAlignment() const override {
     if (STI->hasFeature(RISCV::FeatureStdExtC) ||
         STI->hasFeature(RISCV::FeatureStdExtZca))
diff --git a/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp
index b38179a36ea2d..2df269a25af8b 100644
--- a/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp
+++ b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp
@@ -7,11 +7,13 @@
 //===----------------------------------------------------------------------===//
 
 #include "RISCVMCSymbolizer.h"
+#include "MCTargetDesc/RISCVMCTargetDesc.h"
 #include "bolt/Core/BinaryContext.h"
 #include "bolt/Core/BinaryFunction.h"
 #include "bolt/Core/MCPlusBuilder.h"
 #include "bolt/Core/Relocation.h"
 #include "llvm/BinaryFormat/ELF.h"
+#include "llvm/MC/MCDisassembler/MCDisassembler.h"
 #include "llvm/MC/MCInst.h"
 
 #define DEBUG_TYPE "bolt-symbolizer"
@@ -98,8 +100,50 @@ bool RISCVMCSymbolizer::tryAddingSymbolicOperand(
   // handling moved into the symbolizer.
   const Relocation *Rel =
       Function.getRelocationInRange(InstOffset, InstOffset + InstSize);
-  if (!Rel)
-    return false;
+  if (!Rel) {
+    // Recover RV64 linker-resolved intra-section calls without relocations.
+    // Decode the following JALR and attach a call expression to the AUIPC
+    // before function reordering can move the caller relative to the callee.
+    if (Inst.getOpcode() != RISCV::AUIPC || !CreateNewSymbols ||
+        !BC.TheTriple->isRISCV64() || InstOffset + 8 > Function.getSize() ||
+        Function.isDataInCodeAt(InstOffset + 4) ||
+        Function.getRelocationInRange(InstOffset, InstOffset + 8))
+      return false;
+
+    ErrorOr<ArrayRef<uint8_t>> FunctionData = Function.getData();
+    if (!FunctionData)
+      return false;
+
+    MCInst JALR;
+    uint64_t JALRSize = 0;
+    if (!BC.DisAsm->getInstruction(JALR, JALRSize,
+                                   FunctionData->slice(InstOffset + 4),
+                                   InstAddress + 4, nulls()) ||
+        JALRSize != 4)
+      return false;
+
+    MCInst AUIPC = Inst;
+    AUIPC.addOperand(MCOperand::createImm(Value));
+    if (!BC.MIB->isUnsymbolizedRISCVCall(AUIPC, JALR))
+      return false;
+
+    const uint64_t Target =
+        InstAddress + BC.MIB->getUnsymbolizedRISCVCallOffset(AUIPC, JALR);
+    BinaryFunction *TargetBF = BC.getBinaryFunctionContainingAddress(Target);
+    if (!TargetBF)
+      return false;
+
+    BC.addInterproceduralReference(&Function, Target);
+    MCSymbol *TargetSymbol =
+        BC.handleExternalBranchTarget(Target, Function, *TargetBF);
+    if (!TargetSymbol)
+      return false;
+
+    const MCExpr *Expr = MCSymbolRefExpr::create(TargetSymbol, *Ctx);
+    Inst.addOperand(MCOperand::createExpr(
+        BC.MIB->getTargetExprFor(Inst, Expr, *Ctx, ELF::R_RISCV_CALL_PLT)));
+    return true;
+  }
 
   MCSymbol *Symbol = Rel->Symbol;
   uint64_t Addend = Rel->Addend;
diff --git a/bolt/test/RISCV/call-relocation-pair.s b/bolt/test/RISCV/call-relocation-pair.s
new file mode 100644
index 0000000000000..ea1c55d1ebcde
--- /dev/null
+++ b/bolt/test/RISCV/call-relocation-pair.s
@@ -0,0 +1,78 @@
+// Test that R_RISCV_CALL and R_RISCV_CALL_PLT cover and decode the complete
+// AUIPC/JALR instruction pair.
+
+// RUN: llvm-mc -triple riscv64 -mattr=-relax -filetype=obj -o %t.o %s
+// RUN: ld.lld --no-relax --emit-relocs -o %t %t.o
+// RUN: llvm-readelf --relocations %t | FileCheck --check-prefix=RELOCS %s
+// RUN: llvm-bolt --print-fix-riscv-calls --print-only=_start -o %t.bolt %t \
+// RUN:     | FileCheck --check-prefix=BOLT %s
+// RUN: llvm-objdump -d %t.bolt | FileCheck --check-prefix=OBJDUMP %s
+
+// RELOCS: R_RISCV_CALL {{.*}} target_call
+// RELOCS: R_RISCV_CALL_PLT {{.*}} target_call_plt
+// RELOCS: R_RISCV_CALL_PLT {{.*}} target_backward
+
+// BOLT-LABEL: Binary Function "_start" after fix-riscv-calls {
+// BOLT:       nop
+// BOLT-NEXT:  call target_call
+// BOLT-NEXT:  nop
+// BOLT-NEXT:  call target_call_plt
+// BOLT-NEXT:  nop
+// BOLT-NEXT:  call target_backward
+
+// OBJDUMP-LABEL: <_start>:
+// OBJDUMP:       nop
+// OBJDUMP-NEXT:  auipc ra,
+// OBJDUMP-NEXT:  jalr {{.*}}(ra)
+// OBJDUMP-NEXT:  nop
+// OBJDUMP-NEXT:  auipc ra,
+// OBJDUMP-NEXT:  jalr {{.*}}(ra)
+// OBJDUMP-NEXT:  nop
+// OBJDUMP-NEXT:  jal {{.*}} <target_backward>
+// OBJDUMP-LABEL: <target_call>:
+// OBJDUMP-LABEL: <target_call_plt>:
+
+  .text
+  .option norvc
+  .option norelax
+
+  .globl target_backward
+  .type target_backward, at function
+target_backward:
+  ret
+  .size target_backward, .-target_backward
+
+  // Put _start more than one page after target_backward so the backwards call
+  // exercises signed high and low immediates.
+  .skip 0x1000
+
+  .globl _start
+  .type _start, at function
+_start:
+  .reloc ., R_RISCV_CALL, target_call
+  auipc ra, 0
+  jalr ra
+  .reloc ., R_RISCV_CALL_PLT, target_call_plt
+  auipc ra, 0
+  jalr ra
+  .reloc ., R_RISCV_CALL_PLT, target_backward
+  auipc ra, 0
+  jalr ra
+  ret
+  .size _start, .-_start
+
+  .skip (1 << 21) + 0x7c
+
+  .globl target_call
+  .type target_call, at function
+target_call:
+  ret
+  .size target_call, .-target_call
+
+  .skip 0x84
+
+  .globl target_call_plt
+  .type target_call_plt, at function
+target_call_plt:
+  ret
+  .size target_call_plt, .-target_call_plt
diff --git a/bolt/test/RISCV/unsymbolized-call-edge.s b/bolt/test/RISCV/unsymbolized-call-edge.s
new file mode 100644
index 0000000000000..8630147d2a803
--- /dev/null
+++ b/bolt/test/RISCV/unsymbolized-call-edge.s
@@ -0,0 +1,116 @@
+// Test signed target reconstruction, JALR target-bit clearing, tail calls,
+// and rejection of AUIPC/JALR near matches without relocations.
+
+// RUN: llvm-mc -triple riscv64 -mattr=-relax -filetype=obj -o %t.o %s
+// RUN: ld.lld --no-relax --emit-relocs -e backward_target -o %t %t.o
+// RUN: echo forward_target > %t.order
+// RUN: echo odd_call >> %t.order
+// RUN: echo near_matches >> %t.order
+// RUN: echo backward_tail >> %t.order
+// RUN: echo backward_target >> %t.order
+// RUN: echo relocated_call >> %t.order
+// RUN: llvm-bolt --print-cfg --print-fix-riscv-calls \
+// RUN:     --print-only=backward_tail --print-only=odd_call \
+// RUN:     --print-only=near_matches --reorder-functions=user \
+// RUN:     --function-order=%t.order \
+// RUN:     -o %t.bolt %t | FileCheck --check-prefix=BOLT %s
+// RUN: llvm-objdump -d %t.bolt | FileCheck --check-prefix=OBJDUMP %s
+// RUN: llvm-mc -triple riscv32 -mattr=-relax -filetype=obj -o %t.32.o %s
+// RUN: ld.lld --no-relax --emit-relocs -e backward_target -o %t.32 %t.32.o
+// RUN: llvm-bolt --print-cfg --print-only=odd_call -o %t.32.bolt %t.32 \
+// RUN:     | FileCheck --check-prefix=RV32 %s
+
+// BOLT-LABEL: Binary Function "backward_tail" after building cfg {
+// BOLT:       auipc t1, backward_target
+// BOLT-NEXT:  jr {{.*}}(t1)
+// BOLT-LABEL: Binary Function "odd_call" after building cfg {
+// BOLT:       auipc ra, forward_target
+// BOLT-NEXT:  jalr {{.*}}(ra)
+// BOLT-LABEL: Binary Function "near_matches" after building cfg {
+// BOLT:       auipc t0, 0x0
+// BOLT-NEXT:  jalr t1
+// BOLT-NEXT:  auipc t0, 0x0
+// BOLT-NEXT:  jalr t0
+// BOLT-LABEL: Binary Function "backward_tail" after fix-riscv-calls {
+// BOLT:       nop
+// BOLT-NEXT:  tail backward_target
+// BOLT-LABEL: Binary Function "odd_call" after fix-riscv-calls {
+// BOLT:       nop
+// BOLT-NEXT:  call forward_target
+// BOLT-LABEL: Binary Function "near_matches" after fix-riscv-calls {
+// BOLT:       auipc t0, 0x0
+// BOLT-NEXT:  jalr t1
+// BOLT-NEXT:  auipc t0, 0x0
+// BOLT-NEXT:  jalr t0
+
+// RV32-LABEL: Binary Function "odd_call" after building cfg {
+// RV32:       auipc ra, 0
+// RV32-NEXT:  jalr 0x89(ra)
+
+// OBJDUMP-LABEL: <odd_call>:
+// OBJDUMP:       jal {{.*}} <forward_target>
+// OBJDUMP-LABEL: <near_matches>:
+// OBJDUMP:       auipc t0, 0x0
+// OBJDUMP-NEXT:  jalr t1
+// OBJDUMP-NEXT:  auipc t0, 0x0
+// OBJDUMP-NEXT:  jalr t0
+// OBJDUMP-LABEL: <backward_tail>:
+// OBJDUMP:       j {{.*}} <backward_target>
+
+  .text
+  .option norvc
+  .option norelax
+
+  .globl backward_target
+  .type backward_target, at function
+backward_target:
+  ret
+  .size backward_target, .-backward_target
+
+  .skip 0x1000
+
+  .globl backward_tail
+  .type backward_tail, at function
+backward_tail:
+  // backward_target is 0x1004 bytes before this AUIPC.
+  auipc t1, 0xfffff
+  jalr zero, -4(t1)
+  .size backward_tail, .-backward_tail
+
+  .globl odd_call
+  .type odd_call, at function
+odd_call:
+  // JALR clears bit zero, so 0x89 targets forward_target at offset 0x88.
+  auipc ra, 0
+  jalr ra, 0x89(ra)
+  ret
+  .size odd_call, .-odd_call
+
+  .skip 0x7c
+
+  .globl forward_target
+  .type forward_target, at function
+forward_target:
+  ret
+  .size forward_target, .-forward_target
+
+  .globl near_matches
+  .type near_matches, at function
+near_matches:
+  // The JALR base does not match the AUIPC destination.
+  auipc t0, 0
+  jalr ra, 0(t1)
+  // The JALR link register is neither zero nor the AUIPC destination.
+  auipc t0, 0
+  jalr ra, 0(t0)
+  ret
+  .size near_matches, .-near_matches
+
+  // Retain a relocation so BOLT can reorder the functions containing the
+  // linker-resolved instruction pairs above.
+  .globl relocated_call
+  .type relocated_call, at function
+relocated_call:
+  call forward_target
+  ret
+  .size relocated_call, .-relocated_call
diff --git a/bolt/test/RISCV/unsymbolized-call-entry.s b/bolt/test/RISCV/unsymbolized-call-entry.s
new file mode 100644
index 0000000000000..e7a8a2199d838
--- /dev/null
+++ b/bolt/test/RISCV/unsymbolized-call-entry.s
@@ -0,0 +1,60 @@
+// Test recovery of an RV64 linker-resolved call targeting an entry point
+// inside a function.
+
+// RUN: llvm-mc -triple riscv64 -mattr=-relax -filetype=obj -o %t.o %s
+// RUN: ld.lld --no-relax --emit-relocs -o %t %t.o
+// RUN: llvm-objdump -dr %t | FileCheck --check-prefix=INPUT %s
+// RUN: echo target > %t.order
+// RUN: echo relocated_call >> %t.order
+// RUN: echo _start >> %t.order
+// RUN: llvm-bolt --print-cfg --print-fix-riscv-calls --print-only=_start \
+// RUN:     --reorder-functions=user \
+// RUN:     --function-order=%t.order \
+// RUN:     -o %t.bolt %t | FileCheck --check-prefix=BOLT %s
+// RUN: llvm-objdump -d %t.bolt | FileCheck --check-prefix=OBJDUMP %s
+
+// INPUT-LABEL: <_start>:
+// INPUT:       auipc ra, 0x200
+// INPUT-NEXT:  jalr 0x8c(ra) <target_entry>
+
+// BOLT-LABEL: Binary Function "_start" after building cfg {
+// BOLT:       auipc ra, {{.*}}target_entry{{.*}}
+// BOLT-NEXT:  jalr {{.*}}(ra)
+// BOLT-LABEL: Binary Function "_start" after fix-riscv-calls {
+// BOLT:       call {{.*}}target_entry{{.*}}
+
+// OBJDUMP-LABEL: <target>:
+// OBJDUMP:       addi a0, a0, {{(0x)?1}}
+// OBJDUMP-LABEL: <target_entry>:
+// OBJDUMP:       ret
+// OBJDUMP-LABEL: <_start>:
+// OBJDUMP:       jal {{.*}} <target_entry>
+
+  .text
+  .option norvc
+  .option norelax
+
+  .globl _start
+  .type _start, at function
+_start:
+  auipc ra, 0x200
+  jalr ra, 0x8c(ra)
+  ret
+  .size _start, .-_start
+
+  .skip (1 << 21) + 0x7c
+
+  .globl target
+  .type target, at function
+target:
+  addi a0, a0, 1
+target_entry:
+  ret
+  .size target, .-target
+
+  .globl relocated_call
+  .type relocated_call, at function
+relocated_call:
+  call target
+  ret
+  .size relocated_call, .-relocated_call
diff --git a/bolt/test/RISCV/unsymbolized-call.s b/bolt/test/RISCV/unsymbolized-call.s
new file mode 100644
index 0000000000000..555852a07b1fa
--- /dev/null
+++ b/bolt/test/RISCV/unsymbolized-call.s
@@ -0,0 +1,60 @@
+// Test recovery of a linker-resolved AUIPC/JALR pair that has no relocation,
+// even though the rest of the executable retains relocations.
+
+// RUN: llvm-mc -triple riscv64 -mattr=-relax -filetype=obj -o %t.o %s
+// RUN: ld.lld --no-relax --emit-relocs -o %t %t.o
+// RUN: llvm-objdump -dr %t | FileCheck --check-prefix=INPUT %s
+// RUN: echo target > %t.order
+// RUN: echo relocated_call >> %t.order
+// RUN: echo _start >> %t.order
+// RUN: llvm-bolt --print-cfg --print-fix-riscv-calls --print-only=_start \
+// RUN:     --reorder-functions=user \
+// RUN:     --function-order=%t.order \
+// RUN:     -o %t.bolt %t | FileCheck --check-prefix=BOLT %s
+// RUN: llvm-objdump -d %t.bolt | FileCheck --check-prefix=OBJDUMP %s
+
+// INPUT-LABEL: <_start>:
+// INPUT:       auipc ra, 0x200
+// INPUT-NEXT:  jalr 0x88(ra) <target>
+// INPUT-NEXT:  ret
+// INPUT-LABEL: <relocated_call>:
+// INPUT:       R_RISCV_CALL_PLT target
+
+// BOLT-LABEL: Binary Function "_start" after building cfg {
+// BOLT:       auipc ra, target
+// BOLT-NEXT:  jalr {{.*}}(ra)
+// BOLT-LABEL: Binary Function "_start" after fix-riscv-calls {
+// BOLT:       call target
+
+// OBJDUMP-LABEL: <target>:
+// OBJDUMP-LABEL: <_start>:
+// OBJDUMP:       jal {{.*}} <target>
+
+  .text
+  .option norvc
+  .option norelax
+
+  .globl _start
+  .type _start, at function
+_start:
+  // The target starts 0x200088 bytes after this AUIPC. Spell out the resolved
+  // immediates so this pair has no relocation, as happens after LTO linking.
+  auipc ra, 0x200
+  jalr ra, 0x88(ra)
+  ret
+  .size _start, .-_start
+
+  .skip (1 << 21) + 0x7c
+
+  .globl target
+  .type target, at function
+target:
+  ret
+  .size target, .-target
+
+  .globl relocated_call
+  .type relocated_call, at function
+relocated_call:
+  call target
+  ret
+  .size relocated_call, .-relocated_call



More information about the llvm-commits mailing list