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

Alexander Wilson via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 21 12:52:47 PDT 2026


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

>From 5292bff12cb2b352fc092f55f053f2f6d2509a99 Mon Sep 17 00:00:00 2001
From: shijinrui <shijinrui at bytedance.com>
Date: Fri, 21 Aug 2026 23:21:06 +0800
Subject: [PATCH 1/2] [BOLT][RISCV] Add target symbolizer for relocations

---
 bolt/lib/Core/BinaryFunction.cpp             |  50 -------
 bolt/lib/Core/Relocation.cpp                 |   2 +-
 bolt/lib/Target/RISCV/CMakeLists.txt         |   2 +
 bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp |   7 +
 bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp  | 150 +++++++++++++++++++
 bolt/lib/Target/RISCV/RISCVMCSymbolizer.h    |  58 +++++++
 bolt/test/RISCV/reloc-bb-split-rv32.s        |  12 +-
 bolt/test/RISCV/reloc-bb-split.s             |  12 +-
 bolt/test/RISCV/reloc-got-moved-rv32.s       |  35 +++++
 bolt/test/RISCV/reloc-got-moved.s            |  35 +++++
 bolt/test/RISCV/reloc-got.s                  |  26 ++--
 bolt/test/RISCV/reloc-pcrel-moved-rv32.s     |  31 ++++
 bolt/test/RISCV/reloc-pcrel-moved.s          |  31 ++++
 bolt/test/RISCV/reloc-pcrel-rv32.s           |  11 +-
 bolt/test/RISCV/reloc-pcrel.s                |   3 +-
 15 files changed, 382 insertions(+), 83 deletions(-)
 create mode 100644 bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp
 create mode 100644 bolt/lib/Target/RISCV/RISCVMCSymbolizer.h
 create mode 100644 bolt/test/RISCV/reloc-got-moved-rv32.s
 create mode 100644 bolt/test/RISCV/reloc-got-moved.s
 create mode 100644 bolt/test/RISCV/reloc-pcrel-moved-rv32.s
 create mode 100644 bolt/test/RISCV/reloc-pcrel-moved.s

diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index a81fa2f45c206..131221ecfd27f 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -1330,13 +1330,6 @@ Error BinaryFunction::disassemble() {
   // basic block.
   Labels[0] = Ctx->createNamedTempSymbol("BB0");
 
-  // Map offsets in the function to a label that should always point to the
-  // corresponding instruction. This is used for labels that shouldn't point to
-  // the start of a basic block but always to a specific instruction. This is
-  // used, for example, on RISC-V where %pcrel_lo relocations point to the
-  // corresponding %pcrel_hi.
-  LabelsMapType InstructionLabels;
-
   uint64_t Size = 0; // instruction size
   for (uint64_t Offset = 0; Offset < getSize(); Offset += Size) {
     MCInst Instruction;
@@ -1501,42 +1494,6 @@ Error BinaryFunction::disassemble() {
         if (BC.isAArch64())
           handleAArch64IndirectCall(Instruction, Offset);
       }
-    } else if (BC.isRISCV()) {
-      // Check if there's a relocation associated with this instruction.
-      for (auto Itr = Relocations.lower_bound(Offset),
-                ItrE = Relocations.lower_bound(Offset + Size);
-           Itr != ItrE; ++Itr) {
-        const Relocation &Relocation = Itr->second;
-        MCSymbol *Symbol = Relocation.Symbol;
-
-        if (Relocation::isInstructionReference(Relocation.Type)) {
-          uint64_t RefOffset = Relocation.Value - getAddress();
-          LabelsMapType::iterator LI = InstructionLabels.find(RefOffset);
-
-          if (LI == InstructionLabels.end()) {
-            Symbol = BC.Ctx->createNamedTempSymbol();
-            InstructionLabels.emplace(RefOffset, Symbol);
-          } else {
-            Symbol = LI->second;
-          }
-        }
-
-        uint64_t Addend = Relocation.Addend;
-
-        // For GOT relocations, create a reference against GOT entry ignoring
-        // the relocation symbol.
-        if (Relocation::isGOT(Relocation.Type)) {
-          assert(Relocation::isPCRelative(Relocation.Type) &&
-                 "GOT relocation must be PC-relative on RISC-V");
-          Symbol = BC.registerNameAtAddress("__BOLT_got_zero", 0, 0, 0);
-          Addend = Relocation.Value + Relocation.Offset + getAddress();
-        }
-        int64_t Value = Relocation.Value;
-        const bool Result = BC.MIB->replaceImmWithSymbolRef(
-            Instruction, Symbol, Addend, Ctx.get(), Value, Relocation.Type);
-        (void)Result;
-        assert(Result && "cannot replace immediate with relocation");
-      }
     }
 
 add_instruction:
@@ -1578,13 +1535,6 @@ Error BinaryFunction::disassemble() {
   // Scope-boundary markers are only consulted while assigning offsets above.
   DebugScopeBoundaryOffsets.clear();
 
-  for (auto [Offset, Label] : InstructionLabels) {
-    InstrMapType::iterator II = Instructions.find(Offset);
-    assert(II != Instructions.end() && "reference to non-existing instruction");
-
-    BC.MIB->setInstLabel(II->second, Label);
-  }
-
   // Reset symbolizer for the disassembler.
   BC.SymbolicDisAsm->setSymbolizer(nullptr);
 
diff --git a/bolt/lib/Core/Relocation.cpp b/bolt/lib/Core/Relocation.cpp
index b0f6b6ce0eddc..cb6aaa552d05f 100644
--- a/bolt/lib/Core/Relocation.cpp
+++ b/bolt/lib/Core/Relocation.cpp
@@ -892,7 +892,7 @@ bool Relocation::isTLS(uint32_t Type) {
 }
 
 bool Relocation::isInstructionReference(uint32_t Type) {
-  if (Arch != Triple::riscv64)
+  if (Arch != Triple::riscv64 && Arch != Triple::riscv32)
     return false;
 
   switch (Type) {
diff --git a/bolt/lib/Target/RISCV/CMakeLists.txt b/bolt/lib/Target/RISCV/CMakeLists.txt
index 45645a98d132f..e7fa950de29df 100644
--- a/bolt/lib/Target/RISCV/CMakeLists.txt
+++ b/bolt/lib/Target/RISCV/CMakeLists.txt
@@ -1,5 +1,6 @@
 set(LLVM_LINK_COMPONENTS
   MC
+  MCDisassembler
   Support
   RISCVDesc
   )
@@ -19,6 +20,7 @@ endif()
 
 add_llvm_library(LLVMBOLTTargetRISCV
   RISCVMCPlusBuilder.cpp
+  RISCVMCSymbolizer.cpp
 
   NO_EXPORT
   DISABLE_LLVM_LINK_LLVM_DYLIB
diff --git a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
index 1511e4744124a..c6c04ca88aa7a 100644
--- a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
+++ b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
@@ -12,6 +12,7 @@
 
 #include "MCTargetDesc/RISCVMCAsmInfo.h"
 #include "MCTargetDesc/RISCVMCTargetDesc.h"
+#include "RISCVMCSymbolizer.h"
 #include "bolt/Core/MCPlusBuilder.h"
 #include "llvm/BinaryFormat/ELF.h"
 #include "llvm/MC/MCContext.h"
@@ -39,6 +40,12 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
 public:
   using MCPlusBuilder::MCPlusBuilder;
 
+  std::unique_ptr<MCSymbolizer>
+  createTargetSymbolizer(BinaryFunction &Function,
+                         bool CreateNewSymbols) const override {
+    return std::make_unique<RISCVMCSymbolizer>(Function, CreateNewSymbols);
+  }
+
   bool equals(const MCSpecifierExpr &A, const MCSpecifierExpr &B,
               CompFuncTy Comp) const override {
     const auto &RISCVExprA = cast<MCSpecifierExpr>(A);
diff --git a/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp
new file mode 100644
index 0000000000000..9ccbfec9faf8a
--- /dev/null
+++ b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp
@@ -0,0 +1,150 @@
+//===- bolt/Target/RISCV/RISCVMCSymbolizer.cpp ----------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "RISCVMCSymbolizer.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/MCInst.h"
+
+#define DEBUG_TYPE "bolt-symbolizer"
+
+namespace llvm {
+namespace bolt {
+
+RISCVMCSymbolizer::RISCVMCSymbolizer(BinaryFunction &Function,
+                                     bool CreateNewSymbols)
+    : MCSymbolizer(*Function.getBinaryContext().Ctx, nullptr),
+      Function(Function), CreateNewSymbols(CreateNewSymbols) {
+  // Discover instruction references before decoding starts. This lets us
+  // attach a label while decoding the referenced %pcrel_hi instruction even
+  // though its %pcrel_lo user is normally decoded later.
+  for (uint64_t SearchOffset = 0; SearchOffset < Function.getSize();) {
+    const Relocation *Rel =
+        Function.getRelocationInRange(SearchOffset, Function.getSize());
+    if (!Rel)
+      break;
+
+    if (Relocation::isInstructionReference(Rel->Type)) {
+      assert(Rel->Value >= Function.getAddress() &&
+             Rel->Value < Function.getAddress() + Function.getSize() &&
+             "RISC-V instruction reference outside of function");
+      const uint64_t ReferencedOffset = Rel->Value - Function.getAddress();
+      InstructionReferences.try_emplace(ReferencedOffset, Rel);
+      if (CreateNewSymbols)
+        InstructionLabels.try_emplace(ReferencedOffset, nullptr);
+    }
+
+    SearchOffset = Rel->Offset + 1;
+  }
+}
+
+RISCVMCSymbolizer::~RISCVMCSymbolizer() {}
+
+MCSymbol *RISCVMCSymbolizer::getOrCreateInstructionLabel(uint64_t Offset) {
+  auto [It, Inserted] = InstructionLabels.try_emplace(Offset, nullptr);
+  (void)Inserted;
+  if (!It->second)
+    It->second = Ctx.createNamedTempSymbol();
+  return It->second;
+}
+
+uint64_t RISCVMCSymbolizer::getGOTValue(const Relocation &Rel) const {
+  BinaryContext &BC = Function.getBinaryContext();
+  const uint64_t HiAddress = Function.getAddress() + Rel.Offset;
+
+  // A GOT high relocation records a combined high/low value. Locate the low
+  // relocation by its reference back to this AUIPC instead of assuming that
+  // the low instruction is adjacent.
+  auto It = InstructionReferences.find(Rel.Offset);
+  if (It != InstructionReferences.end()) {
+    const Relocation *LoRel = It->second;
+    ErrorOr<uint64_t> HiContents = BC.getUnsignedValueAtAddress(HiAddress, 4);
+    ErrorOr<uint64_t> LoContents =
+        BC.getUnsignedValueAtAddress(Function.getAddress() + LoRel->Offset,
+                                     Relocation::getSizeForType(LoRel->Type));
+    assert(HiContents && LoContents &&
+           "cannot read RISC-V GOT relocation pair");
+
+    return Relocation::extractValue(ELF::R_RISCV_PCREL_HI20, *HiContents,
+                                    HiAddress) +
+           Relocation::extractValue(LoRel->Type, *LoContents,
+                                    Function.getAddress() + LoRel->Offset);
+  }
+
+  return Rel.Value;
+}
+
+bool RISCVMCSymbolizer::tryAddingSymbolicOperand(
+    MCInst &Inst, raw_ostream &CStream, int64_t Value, uint64_t InstAddress,
+    bool IsBranch, uint64_t ImmOffset, uint64_t ImmSize, uint64_t InstSize) {
+  BinaryContext &BC = Function.getBinaryContext();
+  MCContext *Ctx = BC.Ctx.get();
+  const uint64_t InstOffset = InstAddress - Function.getAddress();
+
+  // Branches and calls are resolved by BinaryFunction's target-independent
+  // control-flow handling.
+  if (BC.MIB->isBranch(Inst) || BC.MIB->isCall(Inst))
+    return false;
+
+  // Linker processing of R_RISCV_ALIGN can leave emitted relocations at an
+  // offset inside the instruction they apply to. Match the whole instruction
+  // range, as BinaryFunction::disassemble() did before this target-specific
+  // handling moved into the symbolizer.
+  const Relocation *Rel =
+      Function.getRelocationInRange(InstOffset, InstOffset + InstSize);
+  if (!Rel)
+    return false;
+
+  MCSymbol *Symbol = Rel->Symbol;
+  uint64_t Addend = Rel->Addend;
+
+  if (Relocation::isInstructionReference(Rel->Type)) {
+    if (!CreateNewSymbols)
+      return false;
+    Symbol = getOrCreateInstructionLabel(Rel->Value - Function.getAddress());
+    // The input addend reflects the original AUIPC location. The label now
+    // follows the instruction, so the assembler must derive the low bits from
+    // its new location.
+    Addend = 0;
+  }
+
+  // GOT high relocations name the object stored in the GOT, not the GOT entry
+  // addressed by AUIPC. Preserve the actual entry address using a zero-based
+  // symbol, as the RISC-V emitter reuses the input GOT.
+  if (Relocation::isGOT(Rel->Type)) {
+    assert(Relocation::isPCRelative(Rel->Type) &&
+           "GOT relocation must be PC-relative on RISC-V");
+    Symbol = BC.registerNameAtAddress("__BOLT_got_zero", 0, 0, 0);
+    Addend = getGOTValue(*Rel) + InstAddress;
+  }
+
+  assert(Symbol && "RISC-V relocation without a symbol");
+  const MCExpr *Expr = MCSymbolRefExpr::create(Symbol, *Ctx);
+  if (Addend)
+    Expr = MCBinaryExpr::createAdd(Expr, MCConstantExpr::create(Addend, *Ctx),
+                                   *Ctx);
+  Inst.addOperand(MCOperand::createExpr(
+      BC.MIB->getTargetExprFor(Inst, Expr, *Ctx, Rel->Type)));
+
+  // MC annotations must follow every real operand. Attach the instruction
+  // label only after the symbolized immediate has been appended.
+  if (InstructionLabels.find(InstOffset) != InstructionLabels.end())
+    BC.MIB->setInstLabel(Inst, getOrCreateInstructionLabel(InstOffset));
+
+  return true;
+}
+
+void RISCVMCSymbolizer::tryAddingPcLoadReferenceComment(raw_ostream &CStream,
+                                                        int64_t Value,
+                                                        uint64_t Address) {}
+
+} // namespace bolt
+} // namespace llvm
diff --git a/bolt/lib/Target/RISCV/RISCVMCSymbolizer.h b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.h
new file mode 100644
index 0000000000000..6a1e72684366b
--- /dev/null
+++ b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.h
@@ -0,0 +1,58 @@
+//===- bolt/Target/RISCV/RISCVMCSymbolizer.h --------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef BOLT_TARGET_RISCV_RISCVMCSYMBOLIZER_H
+#define BOLT_TARGET_RISCV_RISCVMCSYMBOLIZER_H
+
+#include "bolt/Core/BinaryFunction.h"
+#include "llvm/MC/MCDisassembler/MCSymbolizer.h"
+#include <map>
+
+namespace llvm {
+namespace bolt {
+
+class RISCVMCSymbolizer : public MCSymbolizer {
+protected:
+  BinaryFunction &Function;
+  bool CreateNewSymbols{true};
+
+  /// Map function offsets referenced by %pcrel_lo relocations to labels that
+  /// must remain attached to the corresponding %pcrel_hi instructions.
+  std::map<uint64_t, MCSymbol *> InstructionLabels;
+
+  /// Map referenced instruction offsets to their %pcrel_lo relocations.
+  std::map<uint64_t, const Relocation *> InstructionReferences;
+
+  MCSymbol *getOrCreateInstructionLabel(uint64_t Offset);
+
+  /// Return the complete PC-relative value for a GOT relocation. The value
+  /// recorded when relocations are read assumes that the low instruction
+  /// immediately follows AUIPC. Reconstruct it from the matching low
+  /// relocation so linker scheduling does not affect symbolization.
+  uint64_t getGOTValue(const Relocation &Rel) const;
+
+public:
+  RISCVMCSymbolizer(BinaryFunction &Function, bool CreateNewSymbols = true);
+
+  RISCVMCSymbolizer(const RISCVMCSymbolizer &) = delete;
+  RISCVMCSymbolizer &operator=(const RISCVMCSymbolizer &) = delete;
+  ~RISCVMCSymbolizer() override;
+
+  bool tryAddingSymbolicOperand(MCInst &Inst, raw_ostream &CStream,
+                                int64_t Value, uint64_t Address, bool IsBranch,
+                                uint64_t Offset, uint64_t OpSize,
+                                uint64_t InstSize) override;
+
+  void tryAddingPcLoadReferenceComment(raw_ostream &CStream, int64_t Value,
+                                       uint64_t Address) override;
+};
+
+} // namespace bolt
+} // namespace llvm
+
+#endif
diff --git a/bolt/test/RISCV/reloc-bb-split-rv32.s b/bolt/test/RISCV/reloc-bb-split-rv32.s
index 0ad3168fb983d..a434f5c71bd63 100644
--- a/bolt/test/RISCV/reloc-bb-split-rv32.s
+++ b/bolt/test/RISCV/reloc-bb-split-rv32.s
@@ -20,10 +20,10 @@ _start:
 /// basic block should start there.
 // CHECK-LABEL: {{^}}.LBB00
 // CHECK: nop
-// CHECK-LABEL: {{^}}.Ltmp0
-// CHECK: auipc t0, %pcrel_hi(d)
-// CHECK-NEXT: lw t0, %pcrel_lo({{.*}})(t0)
-// CHECK-NEXT: j .Ltmp0
+// CHECK: {{^}}[[BRANCH_LABEL:.Ltmp[0-9]+]]
+// CHECK: auipc t0, %pcrel_hi(d) # Label: [[HI_LABEL:.Ltmp[0-9]+]]
+// CHECK-NEXT: lw t0, %pcrel_lo([[HI_LABEL]])(t0)
+// CHECK-NEXT: j [[BRANCH_LABEL]]
   nop
 1:
   auipc t0, %pcrel_hi(d)
@@ -34,8 +34,8 @@ _start:
 /// start there.
 // CHECK-LABEL: {{^}}.LFT0
 // CHECK: nop
-// CHECK: auipc t0, %pcrel_hi(d)
-// CHECK-NEXT: lw t0, %pcrel_lo({{.*}})(t0)
+// CHECK: auipc t0, %pcrel_hi(d) # Label: [[SECOND_HI:.Ltmp[0-9]+]]
+// CHECK-NEXT: lw t0, %pcrel_lo([[SECOND_HI]])(t0)
 // CHECK-NEXT: ret
   nop
 1:
diff --git a/bolt/test/RISCV/reloc-bb-split.s b/bolt/test/RISCV/reloc-bb-split.s
index 23439ee2b8367..f862dd96fd79a 100644
--- a/bolt/test/RISCV/reloc-bb-split.s
+++ b/bolt/test/RISCV/reloc-bb-split.s
@@ -17,10 +17,10 @@ _start:
 /// basic block should start there.
 // CHECK-LABEL: {{^}}.LBB00
 // CHECK: nop
-// CHECK-LABEL: {{^}}.Ltmp0
-// CHECK: auipc t0, %pcrel_hi(d) # Label: .Ltmp1
-// CHECK-NEXT: ld t0, %pcrel_lo(.Ltmp1)(t0)
-// CHECK-NEXT: j .Ltmp0
+// CHECK: {{^}}[[BRANCH_LABEL:.Ltmp[0-9]+]]
+// CHECK: auipc t0, %pcrel_hi(d) # Label: [[HI_LABEL:.Ltmp[0-9]+]]
+// CHECK-NEXT: ld t0, %pcrel_lo([[HI_LABEL]])(t0)
+// CHECK-NEXT: j [[BRANCH_LABEL]]
   nop
 1:
   auipc t0, %pcrel_hi(d)
@@ -31,8 +31,8 @@ _start:
 /// start there.
 // CHECK-LABEL: {{^}}.LFT0
 // CHECK: nop
-// CHECK-NEXT: auipc t0, %pcrel_hi(d) # Label: .Ltmp2
-// CHECK-NEXT: ld t0, %pcrel_lo(.Ltmp2)(t0)
+// CHECK-NEXT: auipc t0, %pcrel_hi(d) # Label: [[SECOND_HI:.Ltmp[0-9]+]]
+// CHECK-NEXT: ld t0, %pcrel_lo([[SECOND_HI]])(t0)
 // CHECK-NEXT: ret
   nop
 1:
diff --git a/bolt/test/RISCV/reloc-got-moved-rv32.s b/bolt/test/RISCV/reloc-got-moved-rv32.s
new file mode 100644
index 0000000000000..f4076a35112e1
--- /dev/null
+++ b/bolt/test/RISCV/reloc-got-moved-rv32.s
@@ -0,0 +1,35 @@
+## Check that the RV32 R_RISCV_GOT_HI20/%pcrel_lo pair is rebuilt when the
+## matching low instruction is not immediately after AUIPC.
+
+# RUN: llvm-mc -triple riscv32 -mattr=+c -filetype=obj -o %t.o %s
+# RUN: ld.lld -q -o %t.exe %t.o
+# RUN: llvm-bolt %t.exe -o %t.bolt -reorder-functions=cdsort --check-encoding
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# CHECK: Disassembly of section .text:
+# CHECK: <_start>:
+# CHECK-NEXT: auipc a0, 0xffc12
+# CHECK-NEXT: li a1, 0x7
+# CHECK-NEXT: li a2, 0x9
+# CHECK-NEXT: lw a0, 0x128(a0)
+# CHECK-NEXT: ret
+
+  .data
+  .p2align 12
+  .globl d
+d:
+  .word 0
+
+  .text
+  .globl _start
+  .type _start, @function
+_start:
+  nop
+1:
+  auipc a0, %got_pcrel_hi(d)
+  addi a1, zero, 7
+  addi a2, zero, 9
+  lw a0, %pcrel_lo(1b)(a0)
+  ret
+  .reloc 0, R_RISCV_NONE
+  .size _start, .-_start
diff --git a/bolt/test/RISCV/reloc-got-moved.s b/bolt/test/RISCV/reloc-got-moved.s
new file mode 100644
index 0000000000000..c11155c657210
--- /dev/null
+++ b/bolt/test/RISCV/reloc-got-moved.s
@@ -0,0 +1,35 @@
+## Check that R_RISCV_GOT_HI20 relocations are re-encoded correctly when the
+## matching %pcrel_lo is not in the instruction immediately after the AUIPC.
+
+# RUN: llvm-mc -triple riscv64 -mattr=+c -filetype=obj -o %t.o %s
+# RUN: ld.lld -q -o %t.exe %t.o
+# RUN: llvm-bolt %t.exe -o %t.bolt -reorder-functions=cdsort --check-encoding
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# CHECK: Disassembly of section .text:
+# CHECK: <_start>:
+# CHECK-NEXT: auipc a0, 0xffc12
+# CHECK-NEXT: li a1, 0x7
+# CHECK-NEXT: li a2, 0x9
+# CHECK-NEXT: ld a0, 0x1e0(a0)
+# CHECK-NEXT: ret
+
+  .data
+  .p2align 12
+  .globl d
+d:
+  .dword 0
+
+  .text
+  .globl _start
+  .type _start, @function
+_start:
+  nop
+1:
+  auipc a0, %got_pcrel_hi(d)
+  addi a1, zero, 7
+  addi a2, zero, 9
+  ld a0, %pcrel_lo(1b)(a0)
+  ret
+  .reloc 0, R_RISCV_NONE
+  .size _start, .-_start
diff --git a/bolt/test/RISCV/reloc-got.s b/bolt/test/RISCV/reloc-got.s
index 1860da3e05a3b..905d8451a6365 100644
--- a/bolt/test/RISCV/reloc-got.s
+++ b/bolt/test/RISCV/reloc-got.s
@@ -1,5 +1,6 @@
 // RUN: %clang %cflags64 -o %t %s
-// RUN: llvm-bolt --print-cfg --print-only=_start -o %t.null %t \
+// RUN: llvm-bolt --check-encoding --print-cfg --print-only=_start \
+// RUN:    -o %t.null %t \
 // RUN:    | FileCheck %s
 
   .data
@@ -22,30 +23,27 @@ _start:
   auipc t0, %got_pcrel_hi(d)
   ld t0, %pcrel_lo(1b)(t0)
 
-/// An unrelated instruction sits between the AUIPC and its load.
-// FIXME: The AUIPC below should also use __BOLT_got_zero+[[GOT]], but BOLT
-// takes the low part from the ADDI instead of from the load that names the
-// AUIPC's label.
-// CHECK-NOT:  __BOLT_got_zero+[[GOT]])
-// CHECK:      addi t2, t2, 0x7ff
-// CHECK-NEXT: ld t1, %pcrel_lo({{\.Ltmp[0-9]+}})(t1)
+/// An unrelated instruction can sit between the AUIPC and its load. The
+/// symbolizer locates the low relocation through its reference to the AUIPC.
+// CHECK:      auipc t1, %pcrel_hi(__BOLT_got_zero+[[GOT]]) # Label: [[HI2:\.Ltmp[0-9]+]]
+// CHECK-NEXT: addi t2, t2, 0x7ff
+// CHECK-NEXT: ld t1, %pcrel_lo([[HI2]])(t1)
 2:
   auipc t1, %got_pcrel_hi(d)
   addi t2, t2, 2047
   ld t1, %pcrel_lo(2b)(t1)
   j .L1
 .L2:
+// CHECK:      ld t1, %pcrel_lo([[HI3:\.Ltmp[0-9]+]])(t1)
+// CHECK-NEXT: j
   ld t1, %pcrel_lo(3f)(t1)
   j .Lexit
 .L1:
   nop
-/// The load lives in another basic block, so nothing follows the AUIPC but
-/// the terminator.
-// FIXME: The AUIPC below should also use __BOLT_got_zero+[[GOT]], but BOLT
-// takes the low part from the jump.
+/// The low relocation can also precede the AUIPC in output basic-block order.
 // CHECK:      nop
-// CHECK-NOT:  __BOLT_got_zero+[[GOT]])
-// CHECK:      j
+// CHECK-NEXT: auipc t1, %pcrel_hi(__BOLT_got_zero+[[GOT]]) # Label: [[HI3]]
+// CHECK-NEXT: j
 3:
   auipc t1, %got_pcrel_hi(d)
   j .L2
diff --git a/bolt/test/RISCV/reloc-pcrel-moved-rv32.s b/bolt/test/RISCV/reloc-pcrel-moved-rv32.s
new file mode 100644
index 0000000000000..ceb98c9508162
--- /dev/null
+++ b/bolt/test/RISCV/reloc-pcrel-moved-rv32.s
@@ -0,0 +1,31 @@
+## Check that RV32 R_RISCV_PCREL_LO12 relocations are re-encoded relative to
+## the moved AUIPC instruction instead of retaining the input addend.
+
+# RUN: llvm-mc -triple riscv32 -mattr=+c -filetype=obj -o %t.o %s
+# RUN: ld.lld -q -o %t.exe %t.o
+# RUN: llvm-bolt %t.exe -o %t.bolt -reorder-functions=cdsort --check-encoding
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# CHECK: Disassembly of section .text:
+# CHECK: <_start>:
+# CHECK-NEXT: auipc a0, 0xffc13
+# CHECK-NEXT: lw a0, 0x0(a0)
+# CHECK-NEXT: ret
+
+  .data
+  .p2align 12
+  .globl d
+d:
+  .word 0
+
+  .text
+  .globl _start
+  .type _start, @function
+_start:
+  nop
+1:
+  auipc a0, %pcrel_hi(d)
+  lw a0, %pcrel_lo(1b)(a0)
+  ret
+  .reloc 0, R_RISCV_NONE
+  .size _start, .-_start
diff --git a/bolt/test/RISCV/reloc-pcrel-moved.s b/bolt/test/RISCV/reloc-pcrel-moved.s
new file mode 100644
index 0000000000000..0b69ac2ca48be
--- /dev/null
+++ b/bolt/test/RISCV/reloc-pcrel-moved.s
@@ -0,0 +1,31 @@
+## Check that R_RISCV_PCREL_LO12 relocations are re-encoded relative to the
+## moved AUIPC instruction instead of retaining the input addend.
+
+# RUN: llvm-mc -triple riscv64 -mattr=+c -filetype=obj -o %t.o %s
+# RUN: ld.lld -q -o %t.exe %t.o
+# RUN: llvm-bolt %t.exe -o %t.bolt -reorder-functions=cdsort --check-encoding
+# RUN: llvm-objdump -d %t.bolt | FileCheck %s
+
+# CHECK: Disassembly of section .text:
+# CHECK: <_start>:
+# CHECK-NEXT: auipc a0, 0xffc13
+# CHECK-NEXT: ld a0, 0x0(a0)
+# CHECK-NEXT: ret
+
+  .data
+  .p2align 12
+  .globl d
+d:
+  .dword 0
+
+  .text
+  .globl _start
+  .type _start, @function
+_start:
+  nop
+1:
+  auipc a0, %pcrel_hi(d)
+  ld a0, %pcrel_lo(1b)(a0)
+  ret
+  .reloc 0, R_RISCV_NONE
+  .size _start, .-_start
diff --git a/bolt/test/RISCV/reloc-pcrel-rv32.s b/bolt/test/RISCV/reloc-pcrel-rv32.s
index 2e386b022b8f8..f78a9d75bf796 100644
--- a/bolt/test/RISCV/reloc-pcrel-rv32.s
+++ b/bolt/test/RISCV/reloc-pcrel-rv32.s
@@ -2,7 +2,8 @@
 
 // RUN: llvm-mc -triple riscv32 -mattr=+c -filetype=obj -o %t.o %s
 // RUN: ld.lld -q -o %t %t.o
-// RUN: llvm-bolt --print-cfg --print-only=_start -o %t.null %t \
+// RUN: llvm-bolt --check-encoding --print-cfg --print-only=_start \
+// RUN:    -o %t.null %t \
 // RUN:    | FileCheck %s
 
   .data
@@ -17,11 +18,11 @@ d:
 // CHECK: Binary Function "_start" after building cfg {
 _start:
   nop // Here to not make the _start and .Ltmp0 symbols coincide
-// CHECK: auipc t0, %pcrel_hi(d)
-// CHECK-NEXT: lw t0, %pcrel_lo({{.*}})(t0)
+// CHECK: auipc t0, %pcrel_hi(d) # Label: [[HI_LABEL:.Ltmp[0-9]+]]
+// CHECK-NEXT: lw t0, %pcrel_lo([[HI_LABEL]])(t0)
   lw t0, d
-// CHECK: auipc t1, %pcrel_hi(d)
-// CHECK-NEXT: sw t0, %pcrel_lo({{.*}})(t1)
+// CHECK: auipc t1, %pcrel_hi(d) # Label: [[SECOND_HI:.Ltmp[0-9]+]]
+// CHECK-NEXT: sw t0, %pcrel_lo([[SECOND_HI]])(t1)
   sw t0, d, t1
   ret
   .size _start, .-_start
diff --git a/bolt/test/RISCV/reloc-pcrel.s b/bolt/test/RISCV/reloc-pcrel.s
index 5320c6d12e5cd..12324b4e112c9 100644
--- a/bolt/test/RISCV/reloc-pcrel.s
+++ b/bolt/test/RISCV/reloc-pcrel.s
@@ -1,5 +1,6 @@
 // RUN: %clang %cflags64 -o %t %s
-// RUN: llvm-bolt --print-cfg --print-only=_start -o %t.null %t \
+// RUN: llvm-bolt --check-encoding --print-cfg --print-only=_start \
+// RUN:    -o %t.null %t \
 // RUN:    | FileCheck %s
 
   .data

>From 4ba86a5d8ebfbe0fde50fc85113ef60c0d4dabcd 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 2/2] [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 intra-section call and tail-call pairs without relocations, including alternate link registers. Relocation-less recovery remains intentionally RV64-only.

This revision addresses review feedback by building on the 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  |  72 +++++++++++-
 bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp   |  48 +++++++-
 .../Inputs/unsymbolized-call-edge-order.txt   |   6 +
 .../RISCV/Inputs/unsymbolized-call-order.txt  |   3 +
 bolt/test/RISCV/call-relocation-pair.s        |  94 +++++++++++++++
 bolt/test/RISCV/unsymbolized-call-edge.s      | 110 ++++++++++++++++++
 bolt/test/RISCV/unsymbolized-call-entry.s     |  57 +++++++++
 bolt/test/RISCV/unsymbolized-call.s           |  57 +++++++++
 11 files changed, 469 insertions(+), 10 deletions(-)
 create mode 100644 bolt/test/RISCV/Inputs/unsymbolized-call-edge-order.txt
 create mode 100644 bolt/test/RISCV/Inputs/unsymbolized-call-order.txt
 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 c6c04ca88aa7a..b9d390046b999 100644
--- a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
+++ b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
@@ -20,6 +20,7 @@
 #include "llvm/MC/MCInstBuilder.h"
 #include "llvm/MC/MCSubtargetInfo.h"
 #include "llvm/Support/ErrorHandling.h"
+#include "llvm/Support/MathExtras.h"
 
 #define DEBUG_TYPE "mcplus"
 
@@ -28,6 +29,33 @@ 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 hasSupportedCallRegisters(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 || Link == Base;
+}
+
 class RISCVMCPlusBuilder : public MCPlusBuilder {
   bool isRV64() const { return STI->hasFeature(RISCV::Feature64Bit); }
   unsigned regSize() const { return isRV64() ? 8 : 4; }
@@ -275,7 +303,20 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
 
   void createCall(MCInst &Inst, const MCSymbol *Target,
                   MCContext *Ctx) override {
-    return createCall(RISCV::PseudoCALL, Inst, Target, Ctx);
+    MCRegister Link = RISCV::X1;
+    if ((Inst.getOpcode() == RISCV::JAL || Inst.getOpcode() == RISCV::JALR ||
+         Inst.getOpcode() == RISCV::PseudoCALLReg) &&
+        Inst.getNumOperands() && Inst.getOperand(0).isReg())
+      Link = Inst.getOperand(0).getReg();
+
+    if (Link == RISCV::X1)
+      return createCall(RISCV::PseudoCALL, Inst, Target, Ctx);
+
+    Inst.setOpcode(RISCV::PseudoCALLReg);
+    Inst.clear();
+    Inst.addOperand(MCOperand::createReg(Link));
+    Inst.addOperand(MCOperand::createExpr(MCSpecifierExpr::create(
+        MCSymbolRefExpr::create(Target, *Ctx), RISCV::S_CALL_PLT, *Ctx)));
   }
 
   void createLongTailCall(InstructionListType &Seq, const MCSymbol *Target,
@@ -483,7 +524,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);
@@ -504,11 +547,30 @@ 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;
+    return hasSupportedCallRegisters(First, Second);
+  }
+
+  bool isUnsymbolizedRISCVCall(const MCInst &First,
+                               const MCInst &Second) const override {
+    if (!isValidUnsymbolizedCallAUIPC(First) ||
+        !isValidUnsymbolizedCallJALR(Second))
+      return false;
+
+    return hasSupportedCallRegisters(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 {
diff --git a/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp
index 9ccbfec9faf8a..d2ed2168478dd 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"
@@ -100,8 +102,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/Inputs/unsymbolized-call-edge-order.txt b/bolt/test/RISCV/Inputs/unsymbolized-call-edge-order.txt
new file mode 100644
index 0000000000000..acaa2eaecb559
--- /dev/null
+++ b/bolt/test/RISCV/Inputs/unsymbolized-call-edge-order.txt
@@ -0,0 +1,6 @@
+forward_target
+odd_call
+near_matches
+backward_tail
+backward_target
+relocated_call
diff --git a/bolt/test/RISCV/Inputs/unsymbolized-call-order.txt b/bolt/test/RISCV/Inputs/unsymbolized-call-order.txt
new file mode 100644
index 0000000000000..801f0aa6638b9
--- /dev/null
+++ b/bolt/test/RISCV/Inputs/unsymbolized-call-order.txt
@@ -0,0 +1,3 @@
+target
+relocated_call
+_start
diff --git a/bolt/test/RISCV/call-relocation-pair.s b/bolt/test/RISCV/call-relocation-pair.s
new file mode 100644
index 0000000000000..09e2ab0899859
--- /dev/null
+++ b/bolt/test/RISCV/call-relocation-pair.s
@@ -0,0 +1,94 @@
+// 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_call_t0
+// 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 t0, target_call_t0
+// 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:  auipc t0,
+// OBJDUMP-NEXT:  jalr t0, {{.*}}(t0)
+// OBJDUMP-NEXT:  nop
+// OBJDUMP-NEXT:  jal {{.*}} <target_backward>
+// OBJDUMP-LABEL: <target_call>:
+// OBJDUMP-LABEL: <target_call_plt>:
+// OBJDUMP-LABEL: <target_call_t0>:
+
+  .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_call_t0
+  auipc t0, 0
+  jalr t0, 0(t0)
+  .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
+
+  .globl target_call_t0
+  .type target_call_t0, at function
+target_call_t0:
+  ret
+  .size target_call_t0, .-target_call_t0
diff --git a/bolt/test/RISCV/unsymbolized-call-edge.s b/bolt/test/RISCV/unsymbolized-call-edge.s
new file mode 100644
index 0000000000000..4889ec9ce7d80
--- /dev/null
+++ b/bolt/test/RISCV/unsymbolized-call-edge.s
@@ -0,0 +1,110 @@
+// 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: 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=%p/Inputs/unsymbolized-call-edge-order.txt \
+// 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..abd51a9f9d385
--- /dev/null
+++ b/bolt/test/RISCV/unsymbolized-call-entry.s
@@ -0,0 +1,57 @@
+// Test recovery of an RV64 linker-resolved call using the alternate link
+// register and 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: llvm-bolt --print-cfg --print-fix-riscv-calls --print-only=_start \
+// RUN:     --reorder-functions=user \
+// RUN:     --function-order=%p/Inputs/unsymbolized-call-order.txt \
+// 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 t0, 0x200
+// INPUT-NEXT:  jalr t0, 0x8c(t0) <target_entry>
+
+// BOLT-LABEL: Binary Function "_start" after building cfg {
+// BOLT:       auipc t0, {{.*}}target_entry{{.*}}
+// BOLT-NEXT:  jalr t0, {{.*}}(t0)
+// BOLT-LABEL: Binary Function "_start" after fix-riscv-calls {
+// BOLT:       call t0, {{.*}}target_entry{{.*}}
+
+// OBJDUMP-LABEL: <target>:
+// OBJDUMP:       addi a0, a0, {{(0x)?1}}
+// OBJDUMP-LABEL: <target_entry>:
+// OBJDUMP:       ret
+// OBJDUMP-LABEL: <_start>:
+// OBJDUMP:       jal t0, {{.*}} <target_entry>
+
+  .text
+  .option norvc
+  .option norelax
+
+  .globl _start
+  .type _start, at function
+_start:
+  auipc t0, 0x200
+  jalr t0, 0x8c(t0)
+  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..d9fb765b22e7f
--- /dev/null
+++ b/bolt/test/RISCV/unsymbolized-call.s
@@ -0,0 +1,57 @@
+// 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: llvm-bolt --print-cfg --print-fix-riscv-calls --print-only=_start \
+// RUN:     --reorder-functions=user \
+// RUN:     --function-order=%p/Inputs/unsymbolized-call-order.txt \
+// 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