Skip to content

[BOLT][RISCV] Add target symbolizer for relocations - #217944

Open
Thrrreeee wants to merge 1 commit into
llvm:mainfrom
Thrrreeee:split-riscv-symbolizer
Open

[BOLT][RISCV] Add target symbolizer for relocations#217944
Thrrreeee wants to merge 1 commit into
llvm:mainfrom
Thrrreeee:split-riscv-symbolizer

Conversation

@Thrrreeee

Copy link
Copy Markdown
Contributor

Depends on #217550, which adds RISC-V disassembler symbolization hooks for UImm20 and SImm12Lo operands.

BOLT currently reconstructs RISC-V relocation-backed operands in BinaryFunction::disassemble() after the instruction has been decoded. This places RISC-V-specific relocation handling in target-independent code and makes it difficult to correctly associate %pcrel_lo relocations with their corresponding %pcrel_hi instructions after code movement.

This change adds a target-specific RISCVMCSymbolizer and installs it through RISCVMCPlusBuilder. Relocation-backed MCExpr operands are now created directly while instructions are being decoded.

@llvmorg-github-actions

llvmorg-github-actions Bot commented Aug 21, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-bolt

@llvm/pr-subscribers-backend-risc-v

Author: Thrrreeee (Thrrreeee)

Changes

Depends on #217550, which adds RISC-V disassembler symbolization hooks for UImm20 and SImm12Lo operands.

BOLT currently reconstructs RISC-V relocation-backed operands in BinaryFunction::disassemble() after the instruction has been decoded. This places RISC-V-specific relocation handling in target-independent code and makes it difficult to correctly associate %pcrel_lo relocations with their corresponding %pcrel_hi instructions after code movement.

This change adds a target-specific RISCVMCSymbolizer and installs it through RISCVMCPlusBuilder. Relocation-backed MCExpr operands are now created directly while instructions are being decoded.


Patch is 23.25 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/217944.diff

15 Files Affected:

  • (modified) bolt/lib/Core/BinaryFunction.cpp (-50)
  • (modified) bolt/lib/Core/Relocation.cpp (+1-1)
  • (modified) bolt/lib/Target/RISCV/CMakeLists.txt (+2)
  • (modified) bolt/lib/Target/RISCV/RISCVMCPlusBuilder.cpp (+7)
  • (added) bolt/lib/Target/RISCV/RISCVMCSymbolizer.cpp (+150)
  • (added) bolt/lib/Target/RISCV/RISCVMCSymbolizer.h (+58)
  • (modified) bolt/test/RISCV/reloc-bb-split-rv32.s (+6-6)
  • (modified) bolt/test/RISCV/reloc-bb-split.s (+6-6)
  • (added) bolt/test/RISCV/reloc-got-moved-rv32.s (+35)
  • (added) bolt/test/RISCV/reloc-got-moved.s (+35)
  • (modified) bolt/test/RISCV/reloc-got.s (+12-14)
  • (added) bolt/test/RISCV/reloc-pcrel-moved-rv32.s (+31)
  • (added) bolt/test/RISCV/reloc-pcrel-moved.s (+31)
  • (modified) bolt/test/RISCV/reloc-pcrel-rv32.s (+6-5)
  • (modified) bolt/test/RISCV/reloc-pcrel.s (+2-1)
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,3...
[truncated]

@@ -0,0 +1,35 @@
## Check that R_RISCV_GOT_HI20 relocations are re-encoded correctly when the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is phrased slightly differently than the reloc-got-moved-rv32.s

rdtscp added a commit to rdtscp/llvm-project that referenced this pull request Aug 21, 2026
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 llvm#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
rdtscp added a commit to rdtscp/llvm-project that referenced this pull request Aug 21, 2026
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 llvm#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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants