Skip to content

[RISCV] Teach RISCVMergeBaseOffset to merge %lo into load/store folding arithmetic#185353

Open
LiqinWeng wants to merge 1 commit intollvm:mainfrom
LiqinWeng:riscv-fold-gp
Open

[RISCV] Teach RISCVMergeBaseOffset to merge %lo into load/store folding arithmetic#185353
LiqinWeng wants to merge 1 commit intollvm:mainfrom
LiqinWeng:riscv-fold-gp

Conversation

@LiqinWeng
Copy link
Contributor

It's possible we have:

  1. first scenarios, the add opcode can be add or add.uw
lui     vr1, %hi(sym)
addi    vr1, vr1, %lo(sym)
add     vr2, vrx, vr1
lbu     vr3, off(vr2)

-----Transform-----

lui     vr1, %hi(sym+off)
add     vr2, vrx, vr1, %gprel_add(sym+off)
lbu     vr3, %gprel_lo(sym+off)(vr2)
  1. second scenarios, the add opcode can be sh1add/sh2add/sh3add/sh1add.uw/sh2add.uw/sh3add.uw
lui     vr1, %hi(sym)
addi    vr1, vr1, %lo(sym)
sh1add  vr2, vrx, vr1
lbu     vr3, off(vr2)

-----Transform-----

lui     vr1, %hi(sym+off)
sh1add  vr2, vrx, vr1, %gprel_shxadd(sym+off)
lbu     vr3, %gprel_lo(sym+off)(vr2)
  1. third scenarios, same as shxadd
addi    vrx, vr0, offAddi
lui     vr1, %hi(sym)
addi    vr1, vr1, %lo(sym)
add     vr2, vrx, vr1
lbu     vr3, off(vr2)

-----Transform-----

lui     vr1, %hi(sym+off+offAddi)
add     vr2, vr0, vr1, %gprel_add(sym+off+offAddi)
lbu     vr3, %gprel_lo(sym+off+offAddi)(vr2)

@llvmbot
Copy link
Member

llvmbot commented Mar 9, 2026

@llvm/pr-subscribers-llvm-binary-utilities

@llvm/pr-subscribers-lld-elf

Author: LiqinWeng (LiqinWeng)

Changes

It's possible we have:

  1. first scenarios, the add opcode can be add or add.uw
lui     vr1, %hi(sym)
addi    vr1, vr1, %lo(sym)
add     vr2, vrx, vr1
lbu     vr3, off(vr2)

-----Transform-----

lui     vr1, %hi(sym+off)
add     vr2, vrx, vr1, %gprel_add(sym+off)
lbu     vr3, %gprel_lo(sym+off)(vr2)
  1. second scenarios, the add opcode can be sh1add/sh2add/sh3add/sh1add.uw/sh2add.uw/sh3add.uw
lui     vr1, %hi(sym)
addi    vr1, vr1, %lo(sym)
sh1add  vr2, vrx, vr1
lbu     vr3, off(vr2)

-----Transform-----

lui     vr1, %hi(sym+off)
sh1add  vr2, vrx, vr1, %gprel_shxadd(sym+off)
lbu     vr3, %gprel_lo(sym+off)(vr2)
  1. third scenarios, same as shxadd
addi    vrx, vr0, offAddi
lui     vr1, %hi(sym)
addi    vr1, vr1, %lo(sym)
add     vr2, vrx, vr1
lbu     vr3, off(vr2)

-----Transform-----

lui     vr1, %hi(sym+off+offAddi)
add     vr2, vr0, vr1, %gprel_add(sym+off+offAddi)
lbu     vr3, %gprel_lo(sym+off+offAddi)(vr2)

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

25 Files Affected:

  • (modified) lld/ELF/Arch/RISCV.cpp (+60-2)
  • (added) lld/test/ELF/riscv-relax-gprel-add.s (+71)
  • (added) lld/test/ELF/riscv-relax-gprel-shxadd.s (+70)
  • (modified) llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV.def (+4)
  • (modified) llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp (+12-1)
  • (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp (+6)
  • (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h (+4)
  • (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp (+8)
  • (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h (+9)
  • (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCAsmInfo.h (+1)
  • (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp (+91-2)
  • (modified) llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCExpr.cpp (+9)
  • (modified) llvm/lib/Target/RISCV/RISCVAsmPrinter.cpp (+9)
  • (modified) llvm/lib/Target/RISCV/RISCVInstrFormats.td (+11)
  • (modified) llvm/lib/Target/RISCV/RISCVInstrInfo.cpp (+3)
  • (modified) llvm/lib/Target/RISCV/RISCVInstrInfo.td (+19)
  • (modified) llvm/lib/Target/RISCV/RISCVInstrInfoZb.td (+17)
  • (modified) llvm/lib/Target/RISCV/RISCVMergeBaseOffset.cpp (+260-1)
  • (added) llvm/test/CodeGen/RISCV/fold-addi-with-add-into-memops.ll (+1232)
  • (modified) llvm/test/CodeGen/RISCV/loop-strength-reduce-add-cheaper-than-mul.ll (+4-6)
  • (modified) llvm/test/CodeGen/RISCV/lpad.ll (+8-12)
  • (modified) llvm/test/MC/RISCV/Relocations/relocations.s (+48)
  • (modified) llvm/test/MC/RISCV/rv32zba-invalid.s (+5)
  • (modified) llvm/test/MC/RISCV/rv64zba-invalid.s (+9)
  • (modified) llvm/test/MC/RISCV/rv64zbb-invalid.s (+1-1)
diff --git a/lld/ELF/Arch/RISCV.cpp b/lld/ELF/Arch/RISCV.cpp
index 85f49c9260565..ccaed6b675d7b 100644
--- a/lld/ELF/Arch/RISCV.cpp
+++ b/lld/ELF/Arch/RISCV.cpp
@@ -74,6 +74,10 @@ class RISCV final : public TargetInfo {
 #define INTERNAL_R_RISCV_GPREL_S 257
 #define INTERNAL_R_RISCV_X0REL_I 258
 #define INTERNAL_R_RISCV_X0REL_S 259
+#define INTERNAL_R_RISCV_GPREL_ADD 260
+#define INTERNAL_R_RISCV_GPREL_SHXADD 261
+#define INTERNAL_R_RISCV_GPREL_ADD_I 262
+#define INTERNAL_R_RISCV_GPREL_ADD_S 263
 
 const uint64_t dtpOffset = 0x800;
 
@@ -289,6 +293,10 @@ RelExpr RISCV::getRelExpr(const RelType type, const Symbol &s,
   case R_RISCV_HI20:
   case R_RISCV_LO12_I:
   case R_RISCV_LO12_S:
+  case R_RISCV_GPREL_ADD:
+  case R_RISCV_GPREL_SHXADD:
+  case R_RISCV_GPREL_LO12_I:
+  case R_RISCV_GPREL_LO12_S:
     return R_ABS;
   case R_RISCV_ADD8:
   case R_RISCV_ADD16:
@@ -499,7 +507,8 @@ void RISCV::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
   case R_RISCV_TLSDESC_LOAD_LO12:
   case R_RISCV_TLSDESC_ADD_LO12:
   case R_RISCV_TPREL_LO12_I:
-  case R_RISCV_LO12_I: {
+  case R_RISCV_LO12_I:
+  case R_RISCV_GPREL_LO12_I: {
     uint64_t hi = (val + 0x800) >> 12;
     uint64_t lo = val - (hi << 12);
     write32le(loc, setLO12_I(read32le(loc), lo & 0xfff));
@@ -508,12 +517,40 @@ void RISCV::relocate(uint8_t *loc, const Relocation &rel, uint64_t val) const {
 
   case R_RISCV_PCREL_LO12_S:
   case R_RISCV_TPREL_LO12_S:
-  case R_RISCV_LO12_S: {
+  case R_RISCV_LO12_S:
+  case R_RISCV_GPREL_LO12_S: {
     uint64_t hi = (val + 0x800) >> 12;
     uint64_t lo = val - (hi << 12);
     write32le(loc, setLO12_S(read32le(loc), lo));
     return;
   }
+  case R_RISCV_GPREL_ADD:
+  case R_RISCV_GPREL_SHXADD: {
+    write32le(loc, read32le(loc));
+    return;
+  }
+
+  case INTERNAL_R_RISCV_GPREL_ADD:
+  case INTERNAL_R_RISCV_GPREL_SHXADD: {
+    uint32_t insn = (read32le(loc) & ~(31 << 20)) | (X_GP << 20);
+    write32le(loc, insn);
+    return;
+  }
+
+  case INTERNAL_R_RISCV_GPREL_ADD_I:
+  case INTERNAL_R_RISCV_GPREL_ADD_S: {
+    Defined *gp = ctx.sym.riscvGlobalPointer;
+    int64_t displace = SignExtend64(val - gp->getVA(ctx), bits);
+    checkInt(ctx, loc, displace, 12, rel);
+    uint32_t insn = read32le(loc);
+    if (rel.type == INTERNAL_R_RISCV_GPREL_ADD_I)
+      insn = setLO12_I(insn, displace);
+    else
+      insn = setLO12_S(insn, displace);
+
+    write32le(loc, insn);
+    return;
+  }
 
   case INTERNAL_R_RISCV_X0REL_I:
   case INTERNAL_R_RISCV_X0REL_S: {
@@ -895,6 +932,19 @@ static void relaxHi20Lo12(Ctx &ctx, const InputSection &sec, size_t i,
   case R_RISCV_LO12_S:
     sec.relaxAux->relocTypes[i] = INTERNAL_R_RISCV_GPREL_S;
     break;
+  
+  case R_RISCV_GPREL_ADD:
+    sec.relaxAux->relocTypes[i] = INTERNAL_R_RISCV_GPREL_ADD;
+    break;
+  case R_RISCV_GPREL_SHXADD:
+    sec.relaxAux->relocTypes[i] = INTERNAL_R_RISCV_GPREL_SHXADD;
+    break;
+  case R_RISCV_GPREL_LO12_I:
+    sec.relaxAux->relocTypes[i] = INTERNAL_R_RISCV_GPREL_ADD_I;
+    break;
+  case R_RISCV_GPREL_LO12_S:
+    sec.relaxAux->relocTypes[i] = INTERNAL_R_RISCV_GPREL_ADD_S;
+    break;
   }
 }
 
@@ -950,6 +1000,10 @@ static bool relax(Ctx &ctx, int pass, InputSection &sec) {
     case R_RISCV_HI20:
     case R_RISCV_LO12_I:
     case R_RISCV_LO12_S:
+    case R_RISCV_GPREL_ADD:
+    case R_RISCV_GPREL_SHXADD:
+    case R_RISCV_GPREL_LO12_I:
+    case R_RISCV_GPREL_LO12_S:
       if (relaxable(relocs, i))
         relaxHi20Lo12(ctx, sec, i, loc, r, remove);
       break;
@@ -1193,6 +1247,10 @@ void RISCV::finalizeRelax(int passes) const {
           case INTERNAL_R_RISCV_GPREL_S:
           case INTERNAL_R_RISCV_X0REL_I:
           case INTERNAL_R_RISCV_X0REL_S:
+          case INTERNAL_R_RISCV_GPREL_ADD:
+          case INTERNAL_R_RISCV_GPREL_SHXADD:
+          case INTERNAL_R_RISCV_GPREL_ADD_I:
+          case INTERNAL_R_RISCV_GPREL_ADD_S:
             break;
           case R_RISCV_RELAX:
             // Used by relaxTlsLe to indicate the relocation is ignored.
diff --git a/lld/test/ELF/riscv-relax-gprel-add.s b/lld/test/ELF/riscv-relax-gprel-add.s
new file mode 100644
index 0000000000000..67c74db3c2f9d
--- /dev/null
+++ b/lld/test/ELF/riscv-relax-gprel-add.s
@@ -0,0 +1,71 @@
+# REQUIRES: riscv
+# RUN: rm -rf %t && split-file %s %t && cd %t
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+relax a.s -o rv32.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+relax a.s -o rv64.o
+
+# RUN: ld.lld --relax-gp --undefined=__global_pointer$ rv32.o lds -o rv32
+# RUN: ld.lld --relax-gp --undefined=__global_pointer$ rv64.o lds -o rv64
+# RUN: llvm-objdump -td -M no-aliases --no-show-raw-insn rv32 | FileCheck %s
+# RUN: llvm-objdump -td -M no-aliases --no-show-raw-insn rv64 | FileCheck %s
+
+# CHECK: 00000000 l       .text {{0*}}0 $x
+
+# CHECK-NOT:  lui
+# CHECK:      addi    a1, a1, -0x800
+# CHECK-NEXT: add     a0, a0, gp
+# CHECK-NEXT: lw      a0, -0x800(a0)
+# CHECK-NEXT: sw      a0, -0x800(a0)
+# CHECK-NOT:  lui
+# CHECK-NEXT: addi    a1, a1, 0x7fa
+# CHECK-NEXT: add     a0, a0, gp
+# CHECK-NEXT: lw      a0, 0x7fa(a0)
+# CHECK-NEXT: sw      a0, 0x7fa(a0)
+# CHECK-NEXT: lui     a1, 0x201
+# CHECK-NEXT: addi    a1, a1, 0xe
+# CHECK-NEXT: add     a0, a0, a1
+# CHECK-NEXT: lw      a0, 0xe(a0)
+# CHECK-NEXT: sw      a0, 0xe(a0)
+# CHECK-EMPTY:
+# CHECK-NEXT: <a>:
+# CHECK-NEXT: addi a0, a0, 0x1
+
+#--- a.s
+.global _start
+_start:
+  slli a0, a0, 2
+  lui  a1, %hi(array)
+  addi a1, a1, %gprel_lo(array)
+  add  a0, a0, a1, %gprel_add(array)
+  lw   a0, %gprel_lo(array)(a0)
+  sw   a0, %gprel_lo(array)(a0)
+  lui  a1, %hi(array1+10)
+  addi a1, a1, %gprel_lo(array1+10)
+  add  a0, a0, a1, %gprel_add(array1+10)
+  lw   a0, %gprel_lo(array1+10)(a0)
+  sw   a0, %gprel_lo(array1+10)(a0)
+  lui  a1, %hi(norelax+10)
+  addi a1, a1, %gprel_lo(norelax+10)
+  add  a0, a0, a1, %gprel_add(norelax+10)
+  lw   a0, %gprel_lo(norelax+10)(a0)
+  sw   a0, %gprel_lo(norelax+10)(a0)
+a:
+  addi a0, a0, 1
+
+.section .sdata,"aw"
+array:
+  .zero   4080
+  .size   array, 4080
+array1:
+  .zero   20
+  .size   array, 20
+norelax:
+  .zero   6
+  .size   array, 6
+
+#--- lds
+SECTIONS {
+  .text : {*(.text) }
+  .sdata 0x200000 : { }
+}
+
diff --git a/lld/test/ELF/riscv-relax-gprel-shxadd.s b/lld/test/ELF/riscv-relax-gprel-shxadd.s
new file mode 100644
index 0000000000000..9dab44b6baa26
--- /dev/null
+++ b/lld/test/ELF/riscv-relax-gprel-shxadd.s
@@ -0,0 +1,70 @@
+# REQUIRES: riscv
+# RUN: rm -rf %t && split-file %s %t && cd %t
+
+# RUN: llvm-mc -filetype=obj -triple=riscv32-unknown-elf -mattr=+relax,+zba a.s -o rv32.o
+# RUN: llvm-mc -filetype=obj -triple=riscv64-unknown-elf -mattr=+relax,+zba a.s -o rv64.o
+
+# RUN: ld.lld --relax-gp --undefined=__global_pointer$ rv32.o lds -o rv32
+# RUN: ld.lld --relax-gp --undefined=__global_pointer$ rv64.o lds -o rv64
+# RUN: llvm-objdump --mattr=+zba -td -M no-aliases --no-show-raw-insn rv32 | FileCheck %s
+# RUN: llvm-objdump --mattr=+zba -td -M no-aliases --no-show-raw-insn rv64 | FileCheck %s
+
+# CHECK: 00000000 l       .text {{0*}}0 $x
+
+# CHECK-NOT:  lui
+# CHECK:      addi    a1, a1, -0x800
+# CHECK-NEXT: sh1add  a0, a0, gp
+# CHECK-NEXT: lw      a0, -0x800(a0)
+# CHECK-NEXT: sw      a0, -0x800(a0)
+# CHECK-NOT:  lui
+# CHECK-NEXT: addi    a1, a1, 0x7fa
+# CHECK-NEXT: sh1add  a0, a0, gp
+# CHECK-NEXT: lw      a0, 0x7fa(a0)
+# CHECK-NEXT: sw      a0, 0x7fa(a0)
+# CHECK-NEXT: lui     a1, 0x201
+# CHECK-NEXT: addi    a1, a1, 0xe
+# CHECK-NEXT: sh1add  a0, a0, a1
+# CHECK-NEXT: lw      a0, 0xe(a0)
+# CHECK-NEXT: sw      a0, 0xe(a0)
+# CHECK-EMPTY:
+# CHECK-NEXT: <a>:
+# CHECK-NEXT: addi a0, a0, 0x1
+
+#--- a.s
+.global _start
+_start:
+  lui  a1, %hi(array)
+  addi a1, a1, %gprel_lo(array)
+  sh1add  a0, a0, a1, %gprel_shxadd(array)
+  lw   a0, %gprel_lo(array)(a0)
+  sw   a0, %gprel_lo(array)(a0)
+  lui  a1, %hi(array1+10)
+  addi a1, a1, %gprel_lo(array1+10)
+  sh1add  a0, a0, a1, %gprel_shxadd(array1+10)
+  lw   a0, %gprel_lo(array1+10)(a0)
+  sw   a0, %gprel_lo(array1+10)(a0)
+  lui  a1, %hi(norelax+10)
+  addi a1, a1, %gprel_lo(norelax+10)
+  sh1add  a0, a0, a1, %gprel_shxadd(norelax+10)
+  lw   a0, %gprel_lo(norelax+10)(a0)
+  sw   a0, %gprel_lo(norelax+10)(a0)
+a:
+  addi a0, a0, 1
+
+.section .sdata,"aw"
+array:
+  .zero   4080
+  .size   array, 4080
+array1:
+  .zero   20
+  .size   array, 20
+norelax:
+  .zero   6
+  .size   array, 6
+
+#--- lds
+SECTIONS {
+  .text : {*(.text) }
+  .sdata 0x200000 : { }
+}
+
diff --git a/llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV.def b/llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV.def
index ac9a089e853a6..a3d8743523ca9 100644
--- a/llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV.def
+++ b/llvm/include/llvm/BinaryFormat/ELFRelocs/RISCV.def
@@ -60,6 +60,10 @@ ELF_RELOC(R_RISCV_TLSDESC_HI20,      62)
 ELF_RELOC(R_RISCV_TLSDESC_LOAD_LO12, 63)
 ELF_RELOC(R_RISCV_TLSDESC_ADD_LO12,  64)
 ELF_RELOC(R_RISCV_TLSDESC_CALL,      65)
+ELF_RELOC(R_RISCV_GPREL_LO12_I,      66)
+ELF_RELOC(R_RISCV_GPREL_LO12_S,      67)
+ELF_RELOC(R_RISCV_GPREL_ADD,         68)
+ELF_RELOC(R_RISCV_GPREL_SHXADD,      69)
 ELF_RELOC(R_RISCV_VENDOR,           191)
 ELF_RELOC(R_RISCV_CUSTOM192,        192)
 ELF_RELOC(R_RISCV_CUSTOM193,        193)
diff --git a/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp b/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
index 443b0b5f3c04b..1468ae35cb96e 100644
--- a/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
+++ b/llvm/lib/Target/RISCV/AsmParser/RISCVAsmParser.cpp
@@ -608,6 +608,17 @@ struct RISCVOperand final : public MCParsedAsmOperand {
            VK == ELF::R_RISCV_TPREL_ADD;
   }
 
+  bool isGPRelAddSymbol() const {
+    int64_t Imm;
+    // Must be of 'immediate' type but not a constant.
+    if (!isExpr() || evaluateConstantExpr(getExpr(), Imm))
+      return false;
+
+    RISCV::Specifier VK = RISCV::S_None;
+    return RISCVAsmParser::classifySymbolRef(getExpr(), VK) &&
+           (VK == ELF::R_RISCV_GPREL_ADD || VK == ELF::R_RISCV_GPREL_SHXADD);
+  }
+
   bool isTLSDESCCallSymbol() const {
     int64_t Imm;
     // Must be of 'immediate' type but not a constant.
@@ -869,7 +880,7 @@ struct RISCVOperand final : public MCParsedAsmOperand {
 
     RISCV::Specifier VK = RISCV::S_None;
     return RISCVAsmParser::classifySymbolRef(getExpr(), VK) &&
-           (VK == RISCV::S_LO || VK == RISCV::S_PCREL_LO ||
+           (VK == RISCV::S_LO || VK == RISCV::S_PCREL_LO || VK == RISCV::S_GPREL_LO ||
             VK == RISCV::S_TPREL_LO || VK == ELF::R_RISCV_TLSDESC_LOAD_LO12 ||
             VK == ELF::R_RISCV_TLSDESC_ADD_LO12);
   }
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
index 0f63b02b54c74..04852901d57b3 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVAsmBackend.cpp
@@ -77,6 +77,10 @@ MCFixupKindInfo RISCVAsmBackend::getFixupKindInfo(MCFixupKind Kind) const {
       {"fixup_riscv_lo12_i", 20, 12, 0},
       {"fixup_riscv_12_i", 20, 12, 0},
       {"fixup_riscv_lo12_s", 0, 32, 0},
+      {"fixup_riscv_gprel_lo12_i", 20, 12, 0},
+      {"fixup_riscv_gprel_lo12_s", 0, 32, 0},
+      {"fixup_riscv_gprel_add", 0, 0, 0},
+      {"fixup_riscv_gprel_shxadd", 0, 0, 0},
       {"fixup_riscv_pcrel_hi20", 12, 20, 0},
       {"fixup_riscv_pcrel_lo12_i", 20, 12, 0},
       {"fixup_riscv_pcrel_lo12_s", 0, 32, 0},
@@ -506,6 +510,7 @@ static uint64_t adjustFixupValue(const MCFixup &Fixup, uint64_t Value,
   case FK_Data_leb128:
     return Value;
   case RISCV::fixup_riscv_lo12_i:
+  case RISCV::fixup_riscv_gprel_lo12_i:
   case RISCV::fixup_riscv_pcrel_lo12_i:
     return Value & 0xfff;
   case RISCV::fixup_riscv_12_i:
@@ -515,6 +520,7 @@ static uint64_t adjustFixupValue(const MCFixup &Fixup, uint64_t Value,
     }
     return Value & 0xfff;
   case RISCV::fixup_riscv_lo12_s:
+  case RISCV::fixup_riscv_gprel_lo12_s:
   case RISCV::fixup_riscv_pcrel_lo12_s:
     return (((Value >> 5) & 0x7f) << 25) | ((Value & 0x1f) << 7);
   case RISCV::fixup_riscv_hi20:
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
index c4d54bdd60737..01b16cee5c599 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVBaseInfo.h
@@ -470,6 +470,10 @@ enum {
   MO_TLSDESC_ADD_LO = 15,
   MO_TLSDESC_CALL = 16,
 
+  MO_GPREL_LO = 17,
+  MO_GPREL_ADD = 18,
+  MO_GPREL_SHXADD = 19,
+
   // Used to differentiate between target-specific "direct" flags and "bitmask"
   // flags. A machine operand can only have one "direct" flag, but can have
   // multiple "bitmask" flags.
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp
index 2885e3cca8722..613c8b1c8ac3c 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVELFObjectWriter.cpp
@@ -135,6 +135,14 @@ unsigned RISCVELFObjectWriter::getRelocType(const MCFixup &Fixup,
     return ELF::R_RISCV_LO12_I;
   case RISCV::fixup_riscv_lo12_s:
     return ELF::R_RISCV_LO12_S;
+  case RISCV::fixup_riscv_gprel_lo12_i:
+    return ELF::R_RISCV_GPREL_LO12_I;
+  case RISCV::fixup_riscv_gprel_lo12_s:
+    return ELF::R_RISCV_GPREL_LO12_S;
+  case RISCV::fixup_riscv_gprel_add:
+    return ELF::R_RISCV_GPREL_ADD;
+  case RISCV::fixup_riscv_gprel_shxadd:
+    return ELF::R_RISCV_GPREL_SHXADD;
   case RISCV::fixup_riscv_rvc_imm:
     reportError(Fixup.getLoc(), "No relocation for CI-type instructions");
     return ELF::R_RISCV_NONE;
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h
index a2b75e4a42e76..f56d54d8fb3f4 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVFixupKinds.h
@@ -24,6 +24,15 @@ enum Fixups {
   fixup_riscv_12_i,
   // 12-bit fixup corresponding to %lo(foo) for the S-type store instructions
   fixup_riscv_lo12_s,
+  // 12-bit fixup corresponding to %gprel_lo(foo) for instructions like addi
+  fixup_riscv_gprel_lo12_i,
+  // 12-bit fixup corresponding to %gprel_lo(foo) for the S-type store
+  // instructions
+  fixup_riscv_gprel_lo12_s,
+  // Fixup corresponding to %gprel_add(foo) for PseudoAddGPRel/PseudoAddUWGPRel, used as a linker hint
+  fixup_riscv_gprel_add,
+  // Fixup corresponding to %gprel_shxadd(foo) for PseudoSh1AddGPRel/PseudoSh2AddGPRel/PseudoSh3AddGPRel/PseudoSh1AddUWGPRel/PseudoSh2AddUWGPRel/PseudoSh3AddUWGPRel, used as a linker hint
+  fixup_riscv_gprel_shxadd,
   // 20-bit fixup corresponding to %pcrel_hi(foo) for instructions like auipc
   fixup_riscv_pcrel_hi20,
   // 12-bit fixup corresponding to %pcrel_lo(foo) for instructions like addi
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCAsmInfo.h b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCAsmInfo.h
index d030c3b5cf867..69823c5adc1a2 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCAsmInfo.h
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCAsmInfo.h
@@ -45,6 +45,7 @@ enum {
   S_TPREL_LO,
   S_CALL_PLT,
   S_GOT_HI,
+  S_GPREL_LO,
   // Vendor-specific relocation types might conflict across vendors.
   // Refer to them using Specifier constants.
   S_QC_ABS20,
diff --git a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
index 4304a1e651ca5..4cead8bf751cf 100644
--- a/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
+++ b/llvm/lib/Target/RISCV/MCTargetDesc/RISCVMCCodeEmitter.cpp
@@ -64,6 +64,9 @@ class RISCVMCCodeEmitter : public MCCodeEmitter {
                       SmallVectorImpl<MCFixup> &Fixups,
                       const MCSubtargetInfo &STI) const;
 
+  void expandAddGPRel(const MCInst &MI, SmallVectorImpl<char> &CB,
+                      SmallVectorImpl<MCFixup> &Fixups,
+                      const MCSubtargetInfo &STI) const;
   void expandLongCondBr(const MCInst &MI, SmallVectorImpl<char> &CB,
                         SmallVectorImpl<MCFixup> &Fixups,
                         const MCSubtargetInfo &STI) const;
@@ -260,6 +263,68 @@ void RISCVMCCodeEmitter::expandAddTPRel(const MCInst &MI,
   support::endian::write(CB, Binary, llvm::endianness::little);
 }
 
+static std::pair<unsigned, uint16_t> getAddOpAndFixups(unsigned AddOp) {
+  switch (AddOp) {
+  default:
+    llvm_unreachable("Unexpected ADD or SHXADD Opcode on GP-relative!");
+  case RISCV::PseudoAddGPRel:
+    return std::make_pair(RISCV::ADD, ELF::R_RISCV_GPREL_ADD);
+  case RISCV::PseudoAddUWGPRel:
+    return std::make_pair(RISCV::ADD_UW, ELF::R_RISCV_GPREL_ADD);
+  case RISCV::PseudoSh1AddGPRel:
+    return std::make_pair(RISCV::SH1ADD, ELF::R_RISCV_GPREL_SHXADD);
+  case RISCV::PseudoSh2AddGPRel:
+    return std::make_pair(RISCV::SH2ADD, ELF::R_RISCV_GPREL_SHXADD);
+  case RISCV::PseudoSh3AddGPRel:
+    return std::make_pair(RISCV::SH3ADD, ELF::R_RISCV_GPREL_SHXADD);
+  case RISCV::PseudoSh1AddUWGPRel:
+    return std::make_pair(RISCV::SH1ADD_UW, ELF::R_RISCV_GPREL_SHXADD);
+  case RISCV::PseudoSh2AddUWGPRel:
+    return std::make_pair(RISCV::SH2ADD_UW, ELF::R_RISCV_GPREL_SHXADD);
+  case RISCV::PseudoSh3AddUWGPRel:
+    return std::make_pair(RISCV::SH3ADD_UW, ELF::R_RISCV_GPREL_SHXADD);
+  }
+}
+
+// PseudoAddGPRel/PseudoAddUWGPRel/PseudoSh1AddGPRel/PseudoSh2AddGPRel/PseudoSh3AddGPRel/PseudoSh1AddUWGPRel/PseudoSh2AddUWGPRel/PseudoSh3AddUWGPRel to a simple ADD or SHXADD with the correct relocation.
+void RISCVMCCodeEmitter::expandAddGPRel(const MCInst &MI,
+                                        SmallVectorImpl<char> &CB,
+                                        SmallVectorImpl<MCFixup> &Fixups,
+                                        const MCSubtargetInfo &STI) const {
+  MCOperand DestReg = MI.getOperand(0);
+  // If the global array can be accessed by GP, src1 or src2 needs to be
+  // replaced with X3 reg in link time.
+  MCOperand Src1 = MI.getOperand(1);
+  MCOperand Src2 = MI.getOperand(2);
+
+  MCOperand SrcSymbol = MI.getOperand(3);
+  assert(SrcSymbol.isExpr() &&
+         "Expected expression as third input to GP-relative add");
+
+  const auto *Expr = dyn_cast<MCSpecifierExpr>(SrcSymbol.getExpr());
+  assert(Expr &&
+         (Expr->getSpecifier() == ELF::R_RISCV_GPREL_ADD ||
+          Expr->getSpecifier() == ELF::R_RISCV_GPREL_SHXADD) &&
+         "Expected gprel_add or gprel_shxadd relocation on GP-relative symbol");
+
+  std::pair<unsigned, uint16_t> Res = getAddOpAndFixups(MI.getOpcode());
+
+  // Emit the correct gprel_add or gprel_shxadd relocation for the symbol.
+  addFixup(Fixups, 0, Expr, Res.second);
+
+  // Emit fixup_riscv_relax for gprel_add where the relax feature is enabled.
+  if (STI.hasFeature(RISCV::FeatureRelax)) {
+    Fixups.back().setLinkerRelaxable();
+  }
+
+  // Emit a normal ADD or SHXADD instruction with the given operands.
+  MCInst TmpInst =
+      MCInstBuilder(Res.first).addOperand(DestReg).addOperand(Src1).addOperand(
+          Src2);
+  uint32_t Binary = getBinaryCodeForInstr(TmpInst, Fixups, STI);
+  support::endian::write(CB, Binary, llvm::endianness::little);
+}
+
 static unsigned getInvertedBranchOp(unsigned BrOp) {
   switch (BrOp) {
   default:
@@ -440,6 +505,17 @@ void RISCVMCCodeEmitter::encodeInstruction(const MCInst &MI,
     expandAddTPRel(MI, CB, Fixups, STI);
     MCNumEmitted += 1;
     return;
+  case RISCV::PseudoAddGPRel:
+  case RISCV::PseudoAddUWGPRel:
+  case RISCV::PseudoSh1AddGPRel:
+  case RISCV::PseudoSh2AddGPRel:
+  case RISCV::PseudoSh3AddGPRel:
+  case RISCV::PseudoSh1AddUWGPRel:
+  case RISCV::PseudoSh2AddUWGPRel:
+  case RISCV::PseudoSh3AddUWGPRel:
+    expandAddGPRel(MI, CB, Fixups, STI);
+    MCNumEmitted += 1;
+    return;
   case RISCV::PseudoLongBEQ:
   case RISCV::PseudoLongBNE:
   case RISCV::PseudoLongBLT:
@@ -636,12 +712,16 @@ uint64_t RISCVMCCodeEmitter::getImmOpValue(const MCInst &MI, unsigned OpNo,
              "invalid specifier");
       break;
     case ELF::R_RISCV_TPREL_ADD:
+    case ELF::R_RISCV_GPREL_ADD:
+    case ELF::R_RISCV_GPREL_SHXADD:
       // tprel_add is only used to indicate that a relocation should be emitted
       // for an add instruction used in TP-relative addressing. It should not be
       // expanded as if representing an actual instruction operand and so to
       // encounter it he...
[truncated]

@wangpc-pp wangpc-pp requested review from kito-cheng and lenary March 9, 2026 06:57
@wangpc-pp
Copy link
Contributor

I don't really get the usage scenarios of these new relocations, I think we should discuss this in psabi first.

@LiqinWeng
Copy link
Contributor Author

LiqinWeng commented Mar 9, 2026

I don't really get the usage scenarios of these new relocations, I think we should discuss this in psabi first.

spec 2006 gcc +4%, perlbench 3%, xalan + 3% in spacmiT X60. There are still issues with the current branch; more detailed information will be provided later. C code like as:

char arr[42];
char foo(int i) {
  return arr[i];
}

char foo1(int i) {
  return arr[i+3];
}

@LiqinWeng
Copy link
Contributor Author

Considering that some users will use clang + gnu ld, I have also implemented it in binutils, and I will upload a commit later.

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

🐧 Linux x64 Test Results

  • 170879 tests passed
  • 3019 tests skipped
  • 3 tests failed

Failed Tests

(click on a test name to see its output)

LLVM

LLVM.CodeGen/RISCV/GlobalISel/add-imm.ll
Exit Code: 2

Command Output (stdout):
--
# RUN: at line 2
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llc -mtriple=riscv32 -global-isel < /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/CodeGen/RISCV/GlobalISel/add-imm.ll  | /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/CodeGen/RISCV/GlobalISel/add-imm.ll -check-prefix=RV32I
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llc -mtriple=riscv32 -global-isel
# .---command stderr------------
# | llc: /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/include/llvm/CodeGen/MachineOperand.h:373: Register llvm::MachineOperand::getReg() const: Assertion `isReg() && "This is not a register operand!"' failed.
# | PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and instructions to reproduce the bug.
# | Stack dump:
# | 0.	Program arguments: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llc -mtriple=riscv32 -global-isel
# | 1.	Running pass 'Function Pass Manager' on module '<stdin>'.
# | 2.	Running pass 'RISC-V Merge Base Offset' on function '@add32_reject'
# |  #0 0x000000000850f448 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/lib/Support/Unix/Signals.inc:880:13
# |  #1 0x000000000850c3f5 llvm::sys::RunSignalHandlers() /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/lib/Support/Signals.cpp:109:18
# |  #2 0x0000000008510261 SignalHandler(int, siginfo_t*, void*) /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/lib/Support/Unix/Signals.inc:448:38
# |  #3 0x0000793d1e7d4330 (/lib/x86_64-linux-gnu/libc.so.6+0x45330)
# |  #4 0x0000793d1e82db2c pthread_kill (/lib/x86_64-linux-gnu/libc.so.6+0x9eb2c)
# |  #5 0x0000793d1e7d427e raise (/lib/x86_64-linux-gnu/libc.so.6+0x4527e)
# |  #6 0x0000793d1e7b78ff abort (/lib/x86_64-linux-gnu/libc.so.6+0x288ff)
# |  #7 0x0000793d1e7b781b (/lib/x86_64-linux-gnu/libc.so.6+0x2881b)
# |  #8 0x0000793d1e7ca517 (/lib/x86_64-linux-gnu/libc.so.6+0x3b517)
# |  #9 0x0000000006211c9d lookupOrInsertIntoBucket<const llvm::MachineInstr *> /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/lib/Target/RISCV/RISCVMergeBaseOffset.cpp:0:0
# | #10 0x0000000006211c9d operator[] /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/include/llvm/ADT/DenseMap.h:354:12
# | #11 0x0000000006211c9d foldIntoMemoryOps /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/lib/Target/RISCV/RISCVMergeBaseOffset.cpp:539:40
# | #12 0x0000000006211c9d (anonymous namespace)::RISCVMergeBaseOffsetOpt::runOnMachineFunction(llvm::MachineFunction&) /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/lib/Target/RISCV/RISCVMergeBaseOffset.cpp:966:21
# | #13 0x00000000071d0513 llvm::MachineFunctionPass::runOnFunction(llvm::Function&) /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/lib/CodeGen/MachineFunctionPass.cpp:0:10
# | #14 0x000000000773e838 llvm::FPPassManager::runOnFunction(llvm::Function&) /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/lib/IR/LegacyPassManager.cpp:0:27
# | #15 0x00000000077462e2 llvm::FPPassManager::runOnModule(llvm::Module&) /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/lib/IR/LegacyPassManager.cpp:1444:13
# | #16 0x000000000773f2ab runOnModule /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/lib/IR/LegacyPassManager.cpp:1513:27
# | #17 0x000000000773f2ab llvm::legacy::PassManagerImpl::run(llvm::Module&) /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/lib/IR/LegacyPassManager.cpp:531:44
# | #18 0x0000000004f2f6fc compileModule(char**, llvm::SmallVectorImpl<llvm::PassPlugin>&, llvm::LLVMContext&, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char>>&) /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/tools/llc/llc.cpp:871:17
# | #19 0x0000000004f2c9c3 main /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/tools/llc/llc.cpp:459:13
# | #20 0x0000793d1e7b91ca (/lib/x86_64-linux-gnu/libc.so.6+0x2a1ca)
# | #21 0x0000793d1e7b928b __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28b)
# | #22 0x0000000004f284e5 _start (/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llc+0x4f284e5)
# `-----------------------------
# error: command failed with exit status: -6
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/CodeGen/RISCV/GlobalISel/add-imm.ll -check-prefix=RV32I
# .---command stderr------------
# | FileCheck error: '<stdin>' is empty.
# | FileCheck command line:  /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/CodeGen/RISCV/GlobalISel/add-imm.ll -check-prefix=RV32I
# `-----------------------------
# error: command failed with exit status: 2

--

LLVM.MC/RISCV/Relocations/mc-dump.s
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 2
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llvm-mc -filetype=obj --triple=riscv64 --mattr=+relax /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/mc-dump.s -debug-only=mc-dump -o /dev/null 2>&1 | /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/mc-dump.s
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llvm-mc -filetype=obj --triple=riscv64 --mattr=+relax /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/mc-dump.s -debug-only=mc-dump -o /dev/null
# note: command had no output on stdout or stderr
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/mc-dump.s
# .---command stderr------------
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/mc-dump.s:10:15: error: CHECK-NEXT: expected string not found in input
# | # CHECK-NEXT: Fixup @0 Value:specifier(4014,ext) Kind:4023 LinkerRelaxable
# |               ^
# | <stdin>:8:56: note: scanning from here
# | 0 Data LinkerRelaxable Size:8 [97,00,00,00,e7,80,00,00]
# |                                                        ^
# | <stdin>:9:2: note: possible intended match here
# |  Fixup @0 Value:specifier(4014,ext) Kind:4027 LinkerRelaxable
# |  ^
# | 
# | Input file: <stdin>
# | Check file: /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/mc-dump.s
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |            1: assembler backend - final-layout 
# |            2: -- 
# |            3: Sections:[ 
# |            4: MCSection Name:.text FirstLinkerRelaxable:1 
# |            5: 0 Align Size:0+0 [] 
# |            6:  Align:4 Fill:0 FillLen:1 MaxBytesToEmit:4 Nops 
# |            7:  Symbol @0 .text 
# |            8: 0 Data LinkerRelaxable Size:8 [97,00,00,00,e7,80,00,00] 
# | next:10'0                                                            X error: no match found
# |            9:  Fixup @0 Value:specifier(4014,ext) Kind:4027 LinkerRelaxable 
# | next:10'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | next:10'1      ?                                                             possible intended match
# |           10:  Symbol @0 $x 
# | next:10'0     ~~~~~~~~~~~~~~
# |           11: 8 Align LinkerRelaxable Size:0+6 [] 
# | next:10'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           12:  Align:8 Fill:0 FillLen:1 MaxBytesToEmit:8 Nops 
# | next:10'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           13:  Fixup @0 Value:6 Kind:2043 
# | next:10'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           14: 14 Align LinkerRelaxable Size:4+6 [13,05,30,00] 
# | next:10'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            .
# |            .
# |            .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

LLVM.MC/RISCV/Relocations/relocations.s
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
/home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llvm-mc -triple riscv32 -M no-aliases /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s -show-encoding      | /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck -check-prefix=INSTR /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/llvm-mc -triple riscv32 -M no-aliases /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s -show-encoding
# .---command stderr------------
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s:165:1: error: instruction requires the following: 'Zba' (Address Generation Instructions), RV64I Base Instruction Set
# | add.uw t1, t1, t2, %gprel_add(foo)
# | ^
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s:169:1: error: instruction requires the following: 'Zba' (Address Generation Instructions)
# | sh1add t1, t1, t2, %gprel_shxadd(foo)
# | ^
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s:173:1: error: instruction requires the following: 'Zba' (Address Generation Instructions), RV64I Base Instruction Set
# | sh1add.uw t1, t1, t2, %gprel_shxadd(foo)
# | ^
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s:177:1: error: instruction requires the following: 'Zba' (Address Generation Instructions)
# | sh2add t1, t1, t2, %gprel_shxadd(foo)
# | ^
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s:181:1: error: instruction requires the following: 'Zba' (Address Generation Instructions), RV64I Base Instruction Set
# | sh2add.uw t1, t1, t2, %gprel_shxadd(foo)
# | ^
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s:185:1: error: instruction requires the following: 'Zba' (Address Generation Instructions)
# | sh3add t1, t1, t2, %gprel_shxadd(foo)
# | ^
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s:189:1: error: instruction requires the following: 'Zba' (Address Generation Instructions), RV64I Base Instruction Set
# | sh3add.uw t1, t1, t2, %gprel_shxadd(foo)
# | ^
# `-----------------------------
# error: command failed with exit status: 1
# executed command: /home/gha/actions-runner/_work/llvm-project/llvm-project/build/bin/FileCheck -check-prefix=INSTR /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s
# .---command stderr------------
# | /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s:167:10: error: INSTR: expected string not found in input
# | # INSTR: add t1, t1, t2, %gprel_add(foo)
# |          ^
# | <stdin>:111:33: note: scanning from here
# |  add t1, t1, t2, %gprel_add(foo) # encoding: [0x33,0x03,0x73,0x00]
# |                                 ^
# | <stdin>:112:16: note: possible intended match here
# |  # fixup A - offset: 0, value: %gprel_add(foo), relocation type: 68
# |                ^
# | 
# | Input file: <stdin>
# | Check file: /home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/test/MC/RISCV/Relocations/relocations.s
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |              .
# |              .
# |              .
# |            106:  # fixup A - offset: 0, value: %pcrel_lo(.L4), kind: fixup_riscv_pcrel_lo12_s 
# |            107:  
# |            108:  add t1, t1, tp, %tprel_add(foo) # encoding: [0x33,0x03,0x43,0x00] 
# |            109:  # fixup A - offset: 0, value: %tprel_add(foo), relocation type: 32 
# |            110:  
# |            111:  add t1, t1, t2, %gprel_add(foo) # encoding: [0x33,0x03,0x73,0x00] 
# | check:167'0                                     X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
# |            112:  # fixup A - offset: 0, value: %gprel_add(foo), relocation type: 68 
# | check:167'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | check:167'1                    ?                                                     possible intended match
# |            113:  
# | check:167'0     ~
# |            114:  
# | check:167'0     ~
# |            115:  
# | check:167'0     ~
# |            116:  
# | check:167'0     ~
# |            117:  
# | check:167'0     ~
# |              .
# |              .
# |              .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

@github-actions
Copy link

github-actions bot commented Mar 9, 2026

🪟 Windows x64 Test Results

  • 131947 tests passed
  • 2983 tests skipped
  • 3 tests failed

Failed Tests

(click on a test name to see its output)

LLVM

LLVM.CodeGen/RISCV/GlobalISel/add-imm.ll
Exit Code: 2

Command Output (stdout):
--
# RUN: at line 2
c:\_work\llvm-project\llvm-project\build\bin\llc.exe -mtriple=riscv32 -global-isel < C:\_work\llvm-project\llvm-project\llvm\test\CodeGen\RISCV\GlobalISel\add-imm.ll  | c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe C:\_work\llvm-project\llvm-project\llvm\test\CodeGen\RISCV\GlobalISel\add-imm.ll -check-prefix=RV32I
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\llc.exe' -mtriple=riscv32 -global-isel
# .---command stderr------------
# | Assertion failed: isReg() && "This is not a register operand!", file C:\_work\llvm-project\llvm-project\llvm\include\llvm/CodeGen/MachineOperand.h, line 373
# | PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace and instructions to reproduce the bug.
# | Stack dump:
# | 0.	Program arguments: c:\\_work\\llvm-project\\llvm-project\\build\\bin\\llc.exe -mtriple=riscv32 -global-isel
# | 1.	Running pass 'Function Pass Manager' on module '<stdin>'.
# | 2.	Running pass 'RISC-V Merge Base Offset' on function '@add32_reject'
# | Exception Code: 0xC000001D
# |  #0 0x00007ff7f73812e6 (c:\_work\llvm-project\llvm-project\build\bin\llc.exe+0x2cf12e6)
# |  #1 0x00007ffaae97bb04 (C:\Windows\System32\ucrtbase.dll+0x7bb04)
# |  #2 0x00007ffaae97cad1 (C:\Windows\System32\ucrtbase.dll+0x7cad1)
# |  #3 0x00007ffaae97e4a1 (C:\Windows\System32\ucrtbase.dll+0x7e4a1)
# |  #4 0x00007ffaae97e6e1 (C:\Windows\System32\ucrtbase.dll+0x7e6e1)
# |  #5 0x00007ff7f602c932 (c:\_work\llvm-project\llvm-project\build\bin\llc.exe+0x199c932)
# |  #6 0x00007ff7f50d5ce3 (c:\_work\llvm-project\llvm-project\build\bin\llc.exe+0xa45ce3)
# |  #7 0x00007ff7f4b48ac9 (c:\_work\llvm-project\llvm-project\build\bin\llc.exe+0x4b8ac9)
# |  #8 0x00007ff7f4b5155d (c:\_work\llvm-project\llvm-project\build\bin\llc.exe+0x4c155d)
# |  #9 0x00007ff7f4b49748 (c:\_work\llvm-project\llvm-project\build\bin\llc.exe+0x4b9748)
# | #10 0x00007ff7f4697908 (c:\_work\llvm-project\llvm-project\build\bin\llc.exe+0x7908)
# | #11 0x00007ff7f4694b76 (c:\_work\llvm-project\llvm-project\build\bin\llc.exe+0x4b76)
# | #12 0x00007ff7f8e48b20 (c:\_work\llvm-project\llvm-project\build\bin\llc.exe+0x47b8b20)
# | #13 0x00007ffab4a94cb0 (C:\Windows\System32\KERNEL32.DLL+0x14cb0)
# | #14 0x00007ffac21bedcb (C:\Windows\SYSTEM32\ntdll.dll+0x7edcb)
# `-----------------------------
# error: command failed with exit status: 0xc000001d
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe' 'C:\_work\llvm-project\llvm-project\llvm\test\CodeGen\RISCV\GlobalISel\add-imm.ll' -check-prefix=RV32I
# .---command stderr------------
# | FileCheck error: '<stdin>' is empty.
# | FileCheck command line:  c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe C:\_work\llvm-project\llvm-project\llvm\test\CodeGen\RISCV\GlobalISel\add-imm.ll -check-prefix=RV32I
# `-----------------------------
# error: command failed with exit status: 2

--

LLVM.MC/RISCV/Relocations/mc-dump.s
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 2
c:\_work\llvm-project\llvm-project\build\bin\llvm-mc.exe -filetype=obj --triple=riscv64 --mattr=+relax C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\mc-dump.s -debug-only=mc-dump -o /dev/null 2>&1 | c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\mc-dump.s
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\llvm-mc.exe' -filetype=obj --triple=riscv64 --mattr=+relax 'C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\mc-dump.s' -debug-only=mc-dump -o /dev/null
# note: command had no output on stdout or stderr
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe' 'C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\mc-dump.s'
# .---command stderr------------
# | C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\mc-dump.s:10:15: error: CHECK-NEXT: expected string not found in input
# | # CHECK-NEXT: Fixup @0 Value:specifier(4014,ext) Kind:4023 LinkerRelaxable
# |               ^
# | <stdin>:8:56: note: scanning from here
# | 0 Data LinkerRelaxable Size:8 [97,00,00,00,e7,80,00,00]
# |                                                        ^
# | <stdin>:9:2: note: possible intended match here
# |  Fixup @0 Value:specifier(4014,ext) Kind:4027 LinkerRelaxable
# |  ^
# | 
# | Input file: <stdin>
# | Check file: C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\mc-dump.s
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |            1: assembler backend - final-layout 
# |            2: -- 
# |            3: Sections:[ 
# |            4: MCSection Name:.text FirstLinkerRelaxable:1 
# |            5: 0 Align Size:0+0 [] 
# |            6:  Align:4 Fill:0 FillLen:1 MaxBytesToEmit:4 Nops 
# |            7:  Symbol @0 .text 
# |            8: 0 Data LinkerRelaxable Size:8 [97,00,00,00,e7,80,00,00] 
# | next:10'0                                                            X error: no match found
# |            9:  Fixup @0 Value:specifier(4014,ext) Kind:4027 LinkerRelaxable 
# | next:10'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | next:10'1      ?                                                             possible intended match
# |           10:  Symbol @0 $x 
# | next:10'0     ~~~~~~~~~~~~~~
# |           11: 8 Align LinkerRelaxable Size:0+6 [] 
# | next:10'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           12:  Align:8 Fill:0 FillLen:1 MaxBytesToEmit:8 Nops 
# | next:10'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           13:  Fixup @0 Value:6 Kind:2043 
# | next:10'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |           14: 14 Align LinkerRelaxable Size:4+6 [13,05,30,00] 
# | next:10'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# |            .
# |            .
# |            .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

LLVM.MC/RISCV/Relocations/relocations.s
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 1
c:\_work\llvm-project\llvm-project\build\bin\llvm-mc.exe -triple riscv32 -M no-aliases C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s -show-encoding      | c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe -check-prefix=INSTR C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\llvm-mc.exe' -triple riscv32 -M no-aliases 'C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s' -show-encoding
# .---command stderr------------
# | C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s:165:1: error: instruction requires the following: 'Zba' (Address Generation Instructions), RV64I Base Instruction Set
# | add.uw t1, t1, t2, %gprel_add(foo)
# | ^
# | C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s:169:1: error: instruction requires the following: 'Zba' (Address Generation Instructions)
# | sh1add t1, t1, t2, %gprel_shxadd(foo)
# | ^
# | C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s:173:1: error: instruction requires the following: 'Zba' (Address Generation Instructions), RV64I Base Instruction Set
# | sh1add.uw t1, t1, t2, %gprel_shxadd(foo)
# | ^
# | C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s:177:1: error: instruction requires the following: 'Zba' (Address Generation Instructions)
# | sh2add t1, t1, t2, %gprel_shxadd(foo)
# | ^
# | C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s:181:1: error: instruction requires the following: 'Zba' (Address Generation Instructions), RV64I Base Instruction Set
# | sh2add.uw t1, t1, t2, %gprel_shxadd(foo)
# | ^
# | C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s:185:1: error: instruction requires the following: 'Zba' (Address Generation Instructions)
# | sh3add t1, t1, t2, %gprel_shxadd(foo)
# | ^
# | C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s:189:1: error: instruction requires the following: 'Zba' (Address Generation Instructions), RV64I Base Instruction Set
# | sh3add.uw t1, t1, t2, %gprel_shxadd(foo)
# | ^
# `-----------------------------
# error: command failed with exit status: 1
# executed command: 'c:\_work\llvm-project\llvm-project\build\bin\filecheck.exe' -check-prefix=INSTR 'C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s'
# .---command stderr------------
# | C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s:167:10: error: INSTR: expected string not found in input
# | # INSTR: add t1, t1, t2, %gprel_add(foo)
# |          ^
# | <stdin>:111:33: note: scanning from here
# |  add t1, t1, t2, %gprel_add(foo) # encoding: [0x33,0x03,0x73,0x00]
# |                                 ^
# | <stdin>:112:16: note: possible intended match here
# |  # fixup A - offset: 0, value: %gprel_add(foo), relocation type: 68
# |                ^
# | 
# | Input file: <stdin>
# | Check file: C:\_work\llvm-project\llvm-project\llvm\test\MC\RISCV\Relocations\relocations.s
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |              .
# |              .
# |              .
# |            106:  # fixup A - offset: 0, value: %pcrel_lo(.L4), kind: fixup_riscv_pcrel_lo12_s 
# |            107:  
# |            108:  add t1, t1, tp, %tprel_add(foo) # encoding: [0x33,0x03,0x43,0x00] 
# |            109:  # fixup A - offset: 0, value: %tprel_add(foo), relocation type: 32 
# |            110:  
# |            111:  add t1, t1, t2, %gprel_add(foo) # encoding: [0x33,0x03,0x73,0x00] 
# | check:167'0                                     X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
# |            112:  # fixup A - offset: 0, value: %gprel_add(foo), relocation type: 68 
# | check:167'0     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# | check:167'1                    ?                                                     possible intended match
# |            113:  
# | check:167'0     ~
# |            114:  
# | check:167'0     ~
# |            115:  
# | check:167'0     ~
# |            116:  
# | check:167'0     ~
# |            117:  
# | check:167'0     ~
# |              .
# |              .
# |              .
# | >>>>>>
# `-----------------------------
# error: command failed with exit status: 1

--

If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the infrastructure label.

@topperc
Copy link
Collaborator

topperc commented Mar 9, 2026

Can we split the llvm and lld parts into separate PRs?

MadeChange |= foldShxaddIntoScaledMemory(Hi, *Lo);
// Non-constant addressing of global array subscripts, which can be
// increase the optimization scenarios of gp-relax
if (Hi.getOpcode() != RISCV::AUIPC && Hi.getOperand(1).isGlobal() && Lo)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Move this check into foldGPIntoMemoryOps to keep this code clean?

if (!isInt<32>(NewOffset))
return false;

// Remove of the addi from add or shxadd, as:
Copy link
Collaborator

Choose a reason for hiding this comment

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

"Remove of the addi" is not grammatically correct.

MachineInstr &AddiOfAdd = *MRI->getVRegDef(Reg);
if (MRI->hasOneUse(Reg) && Reg.isVirtual() &&
(AddiOfAdd.getOpcode() == RISCV::ADDI ||
AddiOfAdd.getOpcode() == RISCV::ADDIW) &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

How do we know it's safe to skip the sign extending behavior of ADDIW?

case R_RISCV_LO12_S:
sec.relaxAux->relocTypes[i] = INTERNAL_R_RISCV_GPREL_S;
break;

Copy link
Collaborator

Choose a reason for hiding this comment

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

Why a blank line here?

ELF_RELOC(R_RISCV_TLSDESC_LOAD_LO12, 63)
ELF_RELOC(R_RISCV_TLSDESC_ADD_LO12, 64)
ELF_RELOC(R_RISCV_TLSDESC_CALL, 65)
ELF_RELOC(R_RISCV_GPREL_LO12_I, 66)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Is there a PR to add these numbers to the psABI?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@topperc
Copy link
Collaborator

topperc commented Mar 9, 2026

Is there a patch for the binutils linker to add these new relocations?

@lenary
Copy link
Member

lenary commented Mar 9, 2026

Broadly, before we can start to accept this, I'd prefer we had a psABI Pull Request with definitions of any new relocations, and examples of the specific relaxations.

I think I did get confused when you started calling these "gprel", as we used to have some "gprel" relocations which were GP-relative. In this case, they're just relative to any other GPR, rather than __global_pointer$ specifically, right? In this case, let's not use "gprel", maybe call them "regrel" or something, for clarity?

Similarly, I think you're using vrN as some shorthand for "virtual" GPRs - it's a little confusing when we also have vector registers.

  1. first scenarios, the add opcode can be add or add.uw
lui     vr1, %hi(sym)
addi    vr1, vr1, %lo(sym)
add     vr2, vrx, vr1
lbu     vr3, off(vr2)

-----Transform-----

lui     vr1, %hi(sym+off)
add     vr2, vrx, vr1, %gprel_add(sym+off)
lbu     vr3, %gprel_lo(sym+off)(vr2)

This is reasonably close to the %tprel_add sequence (the tprel sequence is more specific to the thread pointer, and TLS variables in general).

I don't have a problem sticking a relocation on the add/add.uw here, as a generalisation of approximately what's going on for TP-relative relaxation, but for arbitrary other registers. I guess the difficulty ends up being how to make sure we're doing this relaxation where it is valid, especially where we don't have liveness information (in the linker).

What's the expectation for how the transformed sequence will be relaxed in the linker? Presumably we can delete the lui? Is there much else we can do? In order to delete the lui, does vr1 have to be a specific operand of the add?

What's the intention behind supporting add.uw vs addw on rv64? Presumably this will have an effect on what addresses we can actually compute?

  1. second scenarios, the add opcode can be sh1add/sh2add/sh3add/sh1add.uw/sh2add.uw/sh3add.uw
lui     vr1, %hi(sym)
addi    vr1, vr1, %lo(sym)
sh1add  vr2, vrx, vr1
lbu     vr3, off(vr2)

-----Transform-----

lui     vr1, %hi(sym+off)
sh1add  vr2, vrx, vr1, %gprel_shxadd(sym+off)
lbu     vr3, %gprel_lo(sym+off)(vr2)

This feels similar, I have lots of the same questions about:

  • What relaxations does this actually enable?
  • What effect does sh1add vs sh1add.uw have on the addresses we can materialise?
  • Presumably here, vr1 can only be the rs2 operand of shNadd?
  1. third scenarios, same as shxadd
addi    vrx, vr0, offAddi
lui     vr1, %hi(sym)
addi    vr1, vr1, %lo(sym)
add     vr2, vrx, vr1
lbu     vr3, off(vr2)

-----Transform-----

lui     vr1, %hi(sym+off+offAddi)
add     vr2, vr0, vr1, %gprel_add(sym+off+offAddi)
lbu     vr3, %gprel_lo(sym+off+offAddi)(vr2)

Yes, it does seem reasonable to look at (and delete) the addi coming into any register-relative add, to fold it into register indexing. I don't think this specific sequence needs to be run past the ABI group, as it's basically a generalisation of (1).

@LiqinWeng
Copy link
Contributor Author

Yes, it does seem reasonable to look at (and delete) the addi coming into any register-relative add, to fold it into register indexing. I don't think this specific sequence needs to be run past the ABI group, as it's basically a generalisation of (1).

Pls see issue: #185586

@LiqinWeng
Copy link
Contributor Author

I think I did get confused when you started calling these "gprel", as we used to have some "gprel" relocations which were GP-relative. In this case, they're just relative to any other GPR, rather than __global_pointer$ specifically, right? In this case, let's not use "gprel", maybe call them "regrel" or something, for clarity?

if GP Relation, Still related to GP. with this patch, C code is:

char gp[10];
char foo(int i) {
  return gp[i]
}
image As for how to set the relocation type name, we can discuss that later. What do you think?

// 12-bit fixup corresponding to %lo(foo) for the S-type store instructions
fixup_riscv_lo12_s,
// 12-bit fixup corresponding to %gprel_lo(foo) for instructions like addi
fixup_riscv_gprel_lo12_i,
Copy link
Member

Choose a reason for hiding this comment

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

If the relocation specifier always leads to relocations, we don't need these fixups. Just emit the ELF relocations in the first place.

@LiqinWeng
Copy link
Contributor Author

LiqinWeng commented Mar 10, 2026

  • What relaxations does this actually enable?
image
  • What effect does sh1add vs sh1add.uw have on the addresses we can materialise?

Actually, I discovered the shxadd.uw scenario while building the jemalloc library. The implementation mainly involves removing low-address calculations, updating the ld/st instructions, and updating the add instructions depending on whether GP relax is enabled.

  • Presumably here, vr1 can only be the rs2 operand of shNadd?

yes

@lenary
Copy link
Member

lenary commented Mar 10, 2026

I think I did get confused when you started calling these "gprel", as we used to have some "gprel" relocations which were GP-relative. In this case, they're just relative to any other GPR, rather than __global_pointer$ specifically, right? In this case, let's not use "gprel", maybe call them "regrel" or something, for clarity?

if GP Relation, Still related to GP. with this patch, C code is:

char gp[10];
char foo(int i) {
  return gp[i]
}

image As for how to set the relocation type name, we can discuss that later. What do you think?

The problem with your example here is that this is also doing Global Pointer based relaxation, which is a specific thing, and when the ABI refers to gp-rel, they mean "Global Pointer relative". If your example gp variable (also confusingly named!) was in the lowest page, then you would end up with a sequence starting add xN, xN, zero because the LUI had been deleted, and the register it sets would have been replaced with zero.

What I want to see is, for example 1:

lui     vr1, %hi(sym)
addi    vr1, vr1, %lo(sym)
add     vr2, vrx, vr1
lbu     vr3, off(vr2)

# => After Compiler Transformation

lui     vr1, %hi(sym+off)
add     vr2, vrx, vr1, %NAME_add(sym+off)
lbu     vr3, %NAME_lo(sym+off)(vr2)

# => After Linker Relaxation:

# if sym+off is within 12 signed bits of `__global_pointer$`
add vr2, vrx, gp
lbu vr3, <gp-relative-offset>(vr2)

# OR:
# if sym+off is within 12 signed bits of `0`
add vr2, vrx, zero
lbu vr3, <absolute offset>(vr2)

# OR
# <condition>
<instructions>

There are likely multiple different possible results after linker relaxation. All of these will need to be documented in the psABI proposal you prepare. I have done two for you, but maybe there are more, and maybe you want the linker to produce a different result from what I have shown.

You will also need to do the same for case (2).

The psABI doesn't care about your documented case (3), that's a compiler-only transformation (folding the addi immediate into the addend).

@LiqinWeng
Copy link
Contributor Author

lui vr1, %hi(sym)
addi vr1, vr1, %lo(sym)
add vr2, vrx, vr1
lbu vr3, off(vr2)

=> After Compiler Transformation

lui vr1, %hi(sym+off)
add vr2, vrx, vr1, %NAME_add(sym+off)
lbu vr3, %NAME_lo(sym+off)(vr2)

=> After Linker Relaxation:

if sym+off is within 12 signed bits of __global_pointer$

add vr2, vrx, gp
lbu vr3, (vr2)

OR:

if sym+off is within 12 signed bits of 0

add vr2, vrx, zero
lbu vr3, (vr2)

OR

only the first scenario is supported.Pls see: riscv-non-isa/riscv-elf-psabi-doc#489 want to wait until the standards are finalized before updating the current code.

@LiqinWeng
Copy link
Contributor Author

Is there a patch for the binutils linker to add these new relocations?

Pls see: riscv-non-isa/riscv-elf-psabi-doc#489

@topperc
Copy link
Collaborator

topperc commented Mar 20, 2026

How do we prevent the compiler from generating these relocations if binutils (or a version of binutils without support) is being used as the linker.

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.

6 participants