[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