[llvm] [BOLT][RISCV] Fix AUIPC/JALR call rewriting (PR #216882)
Alexander Wilson via llvm-commits
llvm-commits at lists.llvm.org
Thu Aug 20 14:47:44 PDT 2026
https://github.com/rdtscp updated https://github.com/llvm/llvm-project/pull/216882
>From fd0fc88d2ffa46c22b92371b2341627e524e372e Mon Sep 17 00:00:00 2001
From: shijinrui <shijinrui at bytedance.com>
Date: Thu, 20 Aug 2026 17:50:15 +0800
Subject: [PATCH 1/3] [RISCV][Disassembler] Symbolize UImm20 and SImm12Lo
operands
---
.../RISCV/Disassembler/RISCVDisassembler.cpp | 45 +++++++++++++++++++
llvm/lib/Target/RISCV/RISCVInstrInfo.td | 2 +
2 files changed, 47 insertions(+)
diff --git a/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp b/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
index 3b3eb5195a9b4..dbff5b2fc42b7 100644
--- a/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
+++ b/llvm/lib/Target/RISCV/Disassembler/RISCVDisassembler.cpp
@@ -65,17 +65,37 @@ static MCDisassembler *createRISCVDisassembler(const Target &T,
return new RISCVDisassembler(STI, Ctx, T.createMCInstrInfo());
}
+static MCSymbolizer *
+createRISCVMCSymbolizer(const Triple &TT, LLVMOpInfoCallback GetOpInfo,
+ LLVMSymbolLookupCallback /*SymbolLookUp*/,
+ void *DisInfo, MCContext *Ctx,
+ std::unique_ptr<MCRelocationInfo> &&RelInfo) {
+ // RISC-V only asks MCSymbolizer to decode HI20/LO12 address fragments. They
+ // require relocation information and cannot be looked up as absolute
+ // addresses when GetOpInfo fails.
+ return llvm::createMCSymbolizer(TT, GetOpInfo, /*SymbolLookUp=*/nullptr,
+ DisInfo, Ctx, std::move(RelInfo));
+}
+
extern "C" LLVM_ABI LLVM_EXTERNAL_VISIBILITY void
LLVMInitializeRISCVDisassembler() {
// Register the disassembler for each target.
TargetRegistry::RegisterMCDisassembler(getTheRISCV32Target(),
createRISCVDisassembler);
+ TargetRegistry::RegisterMCSymbolizer(getTheRISCV32Target(),
+ createRISCVMCSymbolizer);
TargetRegistry::RegisterMCDisassembler(getTheRISCV64Target(),
createRISCVDisassembler);
+ TargetRegistry::RegisterMCSymbolizer(getTheRISCV64Target(),
+ createRISCVMCSymbolizer);
TargetRegistry::RegisterMCDisassembler(getTheRISCV32beTarget(),
createRISCVDisassembler);
+ TargetRegistry::RegisterMCSymbolizer(getTheRISCV32beTarget(),
+ createRISCVMCSymbolizer);
TargetRegistry::RegisterMCDisassembler(getTheRISCV64beTarget(),
createRISCVDisassembler);
+ TargetRegistry::RegisterMCSymbolizer(getTheRISCV64beTarget(),
+ createRISCVMCSymbolizer);
}
template <unsigned FirstReg, unsigned NumRegsInClass, unsigned RVELimit = 0>
@@ -403,6 +423,31 @@ static DecodeStatus decodeSImmOperand(MCInst &Inst, uint32_t Imm,
return MCDisassembler::Success;
}
+static DecodeStatus decodeSImm12LoOperand(MCInst &Inst, uint32_t Imm,
+ int64_t Address,
+ const MCDisassembler *Decoder) {
+ assert(isUInt<12>(Imm) && "Invalid immediate");
+ const int64_t Value = SignExtend64<12>(Imm);
+ if (!Decoder->tryAddingSymbolicOperand(Inst, Value, Address,
+ /*IsBranch=*/false,
+ /*Offset=*/0, /*OpSize=*/4,
+ /*InstSize=*/4))
+ Inst.addOperand(MCOperand::createImm(Value));
+ return MCDisassembler::Success;
+}
+
+static DecodeStatus decodeUImm20Operand(MCInst &Inst, uint32_t Imm,
+ int64_t Address,
+ const MCDisassembler *Decoder) {
+ assert(isUInt<20>(Imm) && "Invalid immediate");
+ if (!Decoder->tryAddingSymbolicOperand(Inst, Imm, Address,
+ /*IsBranch=*/false,
+ /*Offset=*/0, /*OpSize=*/4,
+ /*InstSize=*/4))
+ Inst.addOperand(MCOperand::createImm(Imm));
+ return MCDisassembler::Success;
+}
+
template <unsigned N>
static DecodeStatus decodeSImmNonZeroOperand(MCInst &Inst, uint32_t Imm,
int64_t Address,
diff --git a/llvm/lib/Target/RISCV/RISCVInstrInfo.td b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
index 5be74d729c5b0..5a37b229664c4 100644
--- a/llvm/lib/Target/RISCV/RISCVInstrInfo.td
+++ b/llvm/lib/Target/RISCV/RISCVInstrInfo.td
@@ -400,6 +400,7 @@ def uimm64 : RISCVUImmOp<64>;
def simm12 : RISCVSImmLeafOp<12>;
class Simm12LoOp : RISCVSImmLeafOp<12> {
+ let DecoderMethod = "decodeSImm12LoOperand";
let MCOperandPredicate = [{
int64_t Imm;
if (MCOp.evaluateAsConstantImm(Imm))
@@ -455,6 +456,7 @@ def bare_simm13_lsb0 : BareSImm13Lsb0MaybeSym,
def bare_simm13_lsb0_bb : BareSImm13Lsb0MaybeSym;
class UImm20OperandMaybeSym : RISCVUImmOp<20> {
+ let DecoderMethod = "decodeUImm20Operand";
let MCOperandPredicate = [{
int64_t Imm;
if (MCOp.evaluateAsConstantImm(Imm))
>From 7482669ce6f32ce08bdde2ac66bea1dc48c56cd8 Mon Sep 17 00:00:00 2001
From: Alexander Wilson <rdtscp at meta.com>
Date: Thu, 20 Aug 2026 14:38:16 -0700
Subject: [PATCH 2/3] [BOLT][RISCV] Fix RV64 AUIPC/JALR call rewriting
Use the RISC-V symbolic-disassembly hooks from #217550 to handle relocation-backed AUIPC/JALR calls in a target MCSymbolizer.
R_RISCV_CALL and R_RISCV_CALL_PLT cover an AUIPC/JALR pair, so read both instructions and combine their signed high and low immediates. Also recover RV64 linker-resolved intra-section call and tail-call pairs without relocations, reconstructing the target according to AUIPC/JALR semantics and preserving alternate link registers.
Relocation-less recovery is deliberately limited to RV64. RV32 target calculation requires XLEN-wrapped address arithmetic and remains out of scope.
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 relocation definitions for R_RISCV_CALL and R_RISCV_CALL_PLT:
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/BinaryFunction.cpp | 9 ++
bolt/lib/Core/Relocation.cpp | 10 +-
bolt/lib/Target/RISCV/CMakeLists.txt | 2 +
bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp | 80 ++++++++++++-
bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp | 93 +++++++++++++++
bolt/lib/Target/RISCV/RISCVMCSymbolizer.h | 39 +++++++
.../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 +++++++++
14 files changed, 574 insertions(+), 8 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/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/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index a81fa2f45c206..60292055db053 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -24,6 +24,7 @@
#include "llvm/ADT/SmallSet.h"
#include "llvm/ADT/StringExtras.h"
#include "llvm/ADT/StringRef.h"
+#include "llvm/BinaryFormat/ELF.h"
#include "llvm/Demangle/Demangle.h"
#include "llvm/MC/MCAsmInfo.h"
#include "llvm/MC/MCContext.h"
@@ -1507,6 +1508,14 @@ Error BinaryFunction::disassemble() {
ItrE = Relocations.lower_bound(Offset + Size);
Itr != ItrE; ++Itr) {
const Relocation &Relocation = Itr->second;
+
+ if (Relocation.Type == ELF::R_RISCV_CALL ||
+ Relocation.Type == ELF::R_RISCV_CALL_PLT) {
+ assert(BC.MIB->getTargetSymbol(Instruction) &&
+ "RISC-V call should be symbolized by RISCVMCSymbolizer");
+ continue;
+ }
+
MCSymbol *Symbol = Relocation.Symbol;
if (Relocation::isInstructionReference(Relocation.Type)) {
diff --git a/bolt/lib/Core/Relocation.cpp b/bolt/lib/Core/Relocation.cpp
index b0f6b6ce0eddc..a0a90477885bf 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/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..5202812cd5a60 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"
@@ -19,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"
@@ -27,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 MCPhysReg Base = First.getOperand(0).getReg();
+ if (Second.getOperand(1).getReg() != Base)
+ return false;
+
+ const MCPhysReg 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; }
@@ -39,6 +68,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);
@@ -268,7 +303,20 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
void createCall(MCInst &Inst, const MCSymbol *Target,
MCContext *Ctx) override {
- return createCall(RISCV::PseudoCALL, Inst, Target, Ctx);
+ MCPhysReg 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,
@@ -476,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);
@@ -497,11 +547,31 @@ 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. Mask the
+ // decoded AUIPC operand back to its encoded field before sign extension.
+ const int64_t Hi = SignExtend64<32>(
+ (static_cast<uint64_t>(First.getOperand(1).getImm()) & 0xfffff) << 12);
+ 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
new file mode 100644
index 0000000000000..4339942de2f40
--- /dev/null
+++ b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp
@@ -0,0 +1,93 @@
+//===- 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 "MCTargetDesc/RISCVMCTargetDesc.h"
+#include "bolt/Core/BinaryContext.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/MCExpr.h"
+#include "llvm/MC/MCInst.h"
+
+namespace llvm {
+namespace bolt {
+
+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) {
+ if (Inst.getOpcode() != RISCV::AUIPC)
+ return false;
+
+ BinaryContext &BC = Function.getBinaryContext();
+ MCContext &Ctx = *BC.Ctx;
+ const uint64_t InstOffset = InstAddress - Function.getAddress();
+
+ auto addCallOperand = [&](const MCSymbol *Symbol, int64_t Addend) {
+ 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, ELF::R_RISCV_CALL_PLT)));
+ };
+
+ if (const Relocation *Relocation = Function.getRelocationAt(InstOffset)) {
+ if (Relocation->Type != ELF::R_RISCV_CALL &&
+ Relocation->Type != ELF::R_RISCV_CALL_PLT)
+ return false;
+ addCallOperand(Relocation->Symbol, Relocation->Addend);
+ return true;
+ }
+
+ if (!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;
+
+ addCallOperand(TargetSymbol, /*Addend=*/0);
+ 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..132005e408334
--- /dev/null
+++ b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.h
@@ -0,0 +1,39 @@
+//===- 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"
+
+namespace llvm {
+namespace bolt {
+
+class RISCVMCSymbolizer : public MCSymbolizer {
+ BinaryFunction &Function;
+ bool CreateNewSymbols{true};
+
+public:
+ RISCVMCSymbolizer(BinaryFunction &Function, bool CreateNewSymbols = true)
+ : MCSymbolizer(*Function.getBinaryContext().Ctx, nullptr),
+ Function(Function), CreateNewSymbols(CreateNewSymbols) {}
+
+ 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/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
>From b2b980e083e498b4929b70682b0d0ca07dad6c83 Mon Sep 17 00:00:00 2001
From: Alexander Wilson <rdtscp at meta.com>
Date: Thu, 20 Aug 2026 13:22:23 -0700
Subject: [PATCH 3/3] [BOLT][RISCV] Address symbolizer review feedback
Use the generic RISC-V HI20/LO12 decoder hooks from #217550 and move relocation-backed operand handling into RISCVMCSymbolizer. Pair GOT and PC-relative relocations through their instruction references instead of assuming the low instruction is adjacent.
Retain the RV64 linker-resolved AUIPC/JALR recovery, use MCRegister for decoded registers, and add LIT coverage for non-adjacent GOT and PC-relative pairs.
Assisted-by: Codex
---
bolt/lib/Core/BinaryFunction.cpp | 58 --------
bolt/lib/Core/Relocation.cpp | 2 +-
bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp | 8 +-
bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp | 137 +++++++++++++++++--
bolt/lib/Target/RISCV/RISCVMCSymbolizer.h | 21 ++-
bolt/test/RISCV/reloc-bb-split-rv32.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.s | 31 +++++
bolt/test/RISCV/reloc-pcrel-rv32.s | 11 +-
bolt/test/RISCV/reloc-pcrel.s | 3 +-
12 files changed, 274 insertions(+), 105 deletions(-)
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.s
diff --git a/bolt/lib/Core/BinaryFunction.cpp b/bolt/lib/Core/BinaryFunction.cpp
index 60292055db053..76be85b0119c0 100644
--- a/bolt/lib/Core/BinaryFunction.cpp
+++ b/bolt/lib/Core/BinaryFunction.cpp
@@ -1331,13 +1331,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;
@@ -1502,50 +1495,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;
-
- if (Relocation.Type == ELF::R_RISCV_CALL ||
- Relocation.Type == ELF::R_RISCV_CALL_PLT) {
- assert(BC.MIB->getTargetSymbol(Instruction) &&
- "RISC-V call should be symbolized by RISCVMCSymbolizer");
- continue;
- }
-
- 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:
@@ -1587,13 +1536,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 a0a90477885bf..62ba95511a2c0 100644
--- a/bolt/lib/Core/Relocation.cpp
+++ b/bolt/lib/Core/Relocation.cpp
@@ -896,7 +896,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/RISCVMCPlusBuilder.cpp b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
index 5202812cd5a60..bee610a208337 100644
--- a/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
+++ b/bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp
@@ -48,11 +48,11 @@ bool isValidUnsymbolizedCallJALR(const MCInst &Inst) {
}
bool hasSupportedCallRegisters(const MCInst &First, const MCInst &Second) {
- const MCPhysReg Base = First.getOperand(0).getReg();
+ const MCRegister Base = First.getOperand(0).getReg();
if (Second.getOperand(1).getReg() != Base)
return false;
- const MCPhysReg Link = Second.getOperand(0).getReg();
+ const MCRegister Link = Second.getOperand(0).getReg();
return Link == RISCV::X0 || Link == Base;
}
@@ -303,7 +303,7 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
void createCall(MCInst &Inst, const MCSymbol *Target,
MCContext *Ctx) override {
- MCPhysReg Link = RISCV::X1;
+ MCRegister Link = RISCV::X1;
if ((Inst.getOpcode() == RISCV::JAL || Inst.getOpcode() == RISCV::JALR ||
Inst.getOpcode() == RISCV::PseudoCALLReg) &&
Inst.getNumOperands() && Inst.getOperand(0).isReg())
@@ -899,7 +899,7 @@ class RISCVMCPlusBuilder : public MCPlusBuilder {
.addReg(RISCV::X5)
.addImm((Imm >> 12) & 0xFFFFF));
Insts.emplace_back(
- MCInstBuilder(RISCV::LUI).addReg(RISCV::X6).addImm((Imm)&0xFFF));
+ MCInstBuilder(RISCV::LUI).addReg(RISCV::X6).addImm((Imm) & 0xFFF));
Insts.emplace_back(MCInstBuilder(RISCV::SRLI)
.addReg(RISCV::X6)
.addReg(RISCV::X6)
diff --git a/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp
index 4339942de2f40..01953594cb56e 100644
--- a/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp
+++ b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp
@@ -9,6 +9,7 @@
#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"
@@ -19,35 +20,145 @@
namespace llvm {
namespace bolt {
+RISCVMCSymbolizer::RISCVMCSymbolizer(BinaryFunction &Function,
+ bool CreateNewSymbols)
+ : MCSymbolizer(*Function.getBinaryContext().Ctx, nullptr),
+ Function(Function), CreateNewSymbols(CreateNewSymbols) {
+ if (!CreateNewSymbols)
+ return;
+
+ // 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");
+ InstructionLabels.try_emplace(Rel->Value - Function.getAddress(),
+ nullptr);
+ }
+
+ SearchOffset = Rel->Offset + 1;
+ }
+}
+
+RISCVMCSymbolizer::~RISCVMCSymbolizer() = default;
+
+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.
+ for (uint64_t SearchOffset = 0; SearchOffset < Function.getSize();) {
+ const Relocation *LoRel =
+ Function.getRelocationInRange(SearchOffset, Function.getSize());
+ if (!LoRel)
+ break;
+ SearchOffset = LoRel->Offset + 1;
+
+ if (!Relocation::isInstructionReference(LoRel->Type) ||
+ LoRel->Value != HiAddress)
+ continue;
+
+ 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) {
- if (Inst.getOpcode() != RISCV::AUIPC)
- return false;
-
BinaryContext &BC = Function.getBinaryContext();
MCContext &Ctx = *BC.Ctx;
const uint64_t InstOffset = InstAddress - Function.getAddress();
- auto addCallOperand = [&](const MCSymbol *Symbol, int64_t Addend) {
+ auto addOperand = [&](const MCSymbol *Symbol, int64_t Addend,
+ uint32_t RelType) {
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, ELF::R_RISCV_CALL_PLT)));
+ BC.MIB->getTargetExprFor(Inst, Expr, Ctx, RelType)));
+
+ // 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));
};
- if (const Relocation *Relocation = Function.getRelocationAt(InstOffset)) {
- if (Relocation->Type != ELF::R_RISCV_CALL &&
- Relocation->Type != ELF::R_RISCV_CALL_PLT)
- return false;
- addCallOperand(Relocation->Symbol, Relocation->Addend);
+ // 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) {
+ 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");
+ addOperand(Symbol, Addend, Rel->Type);
return true;
}
- if (!CreateNewSymbols || !BC.TheTriple->isRISCV64() ||
- InstOffset + 8 > Function.getSize() ||
+ // 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;
@@ -81,7 +192,7 @@ bool RISCVMCSymbolizer::tryAddingSymbolicOperand(
if (!TargetSymbol)
return false;
- addCallOperand(TargetSymbol, /*Addend=*/0);
+ addOperand(TargetSymbol, /*Addend=*/0, ELF::R_RISCV_CALL_PLT);
return true;
}
diff --git a/bolt/lib/Target/RISCV/RISCVMCSymbolizer.h b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.h
index 132005e408334..84dd227209c64 100644
--- a/bolt/lib/Target/RISCV/RISCVMCSymbolizer.h
+++ b/bolt/lib/Target/RISCV/RISCVMCSymbolizer.h
@@ -11,18 +11,33 @@
#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;
+
+ MCSymbol *getOrCreateInstructionLabel(uint64_t Offset);
+
+ /// Return the complete PC-relative value for a GOT relocation. Locate the
+ /// low relocation by its reference to the high instruction rather than by
+ /// assuming that the instructions are adjacent.
+ uint64_t getGOTValue(const Relocation &Rel) const;
+
public:
- RISCVMCSymbolizer(BinaryFunction &Function, bool CreateNewSymbols = true)
- : MCSymbolizer(*Function.getBinaryContext().Ctx, nullptr),
- Function(Function), CreateNewSymbols(CreateNewSymbols) {}
+ 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,
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-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.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
More information about the llvm-commits
mailing list