7. RISC-V Relocation Reference
7.1. Introduction
This document describes the RISC-V relocations handled by ELD, including their encoding types, alignment requirements, range checks, and the relaxations that transform instruction sequences at link time.
The official ABI specification is the RISC-V ELF psABI: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
7.2. Relocation Conventions
Symbol |
Meaning |
|---|---|
|
Runtime address of the referenced symbol |
|
Relocation addend |
|
Address of the relocation site |
|
Address of the global pointer ( |
|
Global Offset Table base address |
|
Relocation result before masking/encoding |
7.3. Encoding Types
Each relocation carries an encoding type that describes which instruction
immediate field is written and how the value is packed into it. The
“Immediate bits written” column shows which bits of the computed relocation
value X are placed into which instruction word bits, matching the
encode* helpers in RISCVHelper.h.
7.3.1. Unscrambled (linear) encodings
Encoding |
Instruction format |
Value bits → instruction bits |
Width |
Inst size |
|---|---|---|---|---|
|
No field written |
— |
— |
— |
|
Data byte |
|
8 |
8-bit |
|
Data halfword |
|
16 |
16-bit |
|
Data word |
|
32 |
32-bit |
|
Data doubleword |
|
64 |
64-bit |
|
Byte, low 6 bits |
|
6 |
8-bit |
|
ULEB128 data |
Variable-length unsigned LEB128 |
var |
variable |
7.3.2. 32-bit instruction encodings
Encoding |
Instruction format |
Value bits → instruction bits |
Width |
|---|---|---|---|
|
I-type ( |
|
12 |
|
S-type ( |
|
12 |
|
B-type (branch) |
|
13 (signed, 2-byte aligned) |
|
J-type ( |
|
21 (signed, 2-byte aligned) |
|
U-type ( |
|
20 |
|
Xqci 20-bit U-type |
|
20 |
EncTy_U_HI20carry adjustment:0x800is added before masking so that when the low 12-bitaddiimmediate is sign-extended, it correctly reconstructs the original address together with the upper 20 bits.
7.3.3. 16-bit (RVC) instruction encodings
Encoding |
Instruction format |
Value bits → instruction bits |
Width |
|---|---|---|---|
|
C.B-type (RVC branch) |
|
9 (signed, 2-byte aligned) |
|
C.J-type (RVC jump) |
|
12 (signed, 2-byte aligned) |
|
C.I-type ( |
|
6 |
7.3.4. 48-bit (Xqci/Xqcilo) instruction encodings
Encoding |
Instruction format |
Value bits → instruction bits |
Width |
|---|---|---|---|
|
Xqcilo E-I-type load |
|
26 (signed) |
|
Xqcilo E-S-type store |
|
26 (signed) |
|
Xqci E-B-type branch |
same scramble as |
13 (signed, 2-byte aligned) |
|
Xqci E-J-type call |
|
32 (signed) |
|
Xqci E-AI-type absolute imm |
|
32 (signed) |
7.4. Standard Relocations
7.4.1. Dynamic / Runtime Relocations
These are written by the dynamic linker at load time.
Relocation |
Type |
Encoding |
Operation |
Size |
|---|---|---|---|---|
|
0 |
none |
— |
— |
|
1 |
|
|
32 |
|
2 |
|
|
64 |
|
3 |
none |
|
32 |
|
4 |
none |
Copy from shared object |
32 |
|
5 |
none |
PLT entry |
32 |
|
6 |
none |
TLS module index (32-bit) |
32 |
|
7 |
none |
TLS module index (64-bit) |
64 |
|
8 |
|
TLS DTP offset (32-bit) |
32 |
|
9 |
|
TLS DTP offset (64-bit) |
64 |
|
10 |
|
TLS TP offset (32-bit) |
32 |
|
11 |
|
TLS TP offset (64-bit) |
64 |
7.4.2. Control Flow Relocations
Relocation |
Type |
Encoding |
Operation |
Inst size |
Alignment |
Range check |
|---|---|---|---|---|---|---|
|
12 |
|
|
32-bit |
2 bytes |
|
|
13 |
|
|
32-bit |
2 bytes |
|
|
14 |
|
|
32-bit |
2 bytes |
none (32-bit range via |
|
15 |
|
|
32-bit |
2 bytes |
none (32-bit range via |
|
44 |
|
|
16-bit |
2 bytes |
|
|
45 |
|
|
16-bit |
2 bytes |
|
R_RISCV_CALL/R_RISCV_CALL_PLT: Applied to anauipc+jalrpair (8 bytes). The linker splitsXinto a 20-bit upper part (encoded inauipc) and a 12-bit signed lower part (encoded injalr), with a carry adjustment when bit 11 of the lower part is set. These relocations are the primary candidates for call relaxation (see Relaxations).
7.4.3. PC-Relative Address Relocations
These always appear in pairs: a HI20 relocation on auipc followed by a
LO12 relocation on the instruction that uses the result.
Relocation |
Type |
Encoding |
Operation |
Inst size |
Range check |
|---|---|---|---|---|---|
|
19 |
|
|
32-bit |
none |
|
20 |
|
low 12 bits of paired HI20 |
32-bit |
none |
|
21 |
|
low 12 bits of paired HI20 (stores) |
32-bit |
none |
Pair invariant: The symbol of
R_RISCV_PCREL_LO12_I/Smust point to the address of the correspondingR_RISCV_PCREL_HI20relocation site, not the target symbol. The linker resolves the HI20 first, then propagates its computed value to the LO12.
7.4.4. Absolute Address Relocations
Relocation |
Type |
Encoding |
Operation |
Inst size |
Range check |
|---|---|---|---|---|---|
|
22 |
|
|
32-bit |
|
|
23 |
|
|
32-bit |
none |
|
24 |
|
|
32-bit |
none |
7.4.5. TLS Relocations
Relocation |
Type |
Encoding |
Operation |
Inst size |
|---|---|---|---|---|
|
25 |
|
|
32-bit |
|
26 |
|
low 12 bits of TP offset |
32-bit |
|
27 |
|
low 12 bits of TP offset (stores) |
32-bit |
|
28 |
none |
Relaxation hint (no bits written) |
32-bit |
|
17 |
|
|
32-bit |
|
18 |
|
|
32-bit |
|
58 |
|
|
32-bit |
|
59 |
|
low 12 bits of TLSDESC |
32-bit |
|
60 |
|
low 12 bits of TLSDESC |
32-bit |
|
61 |
none |
TLSDESC call hint |
32-bit |
7.4.6. GOT Relocations
Relocation |
Type |
Encoding |
Operation |
Inst size |
|---|---|---|---|---|
|
16 |
|
|
32-bit |
|
37 |
|
|
32-bit |
7.4.7. Arithmetic / Addend Relocations
Used in debug sections and compact unwind tables to encode label differences.
Relocation |
Type |
Encoding |
Operation |
Size |
|---|---|---|---|---|
|
29 |
|
|
8 |
|
30 |
|
|
16 |
|
31 |
|
|
32 |
|
32 |
|
|
64 |
|
33 |
|
|
8 |
|
34 |
|
|
16 |
|
35 |
|
|
32 |
|
36 |
|
|
64 |
|
52 |
|
|
8 |
|
53 |
|
|
8 |
|
54 |
|
|
8 |
|
55 |
|
|
16 |
|
56 |
|
|
32 |
|
57 |
|
|
32 |
|
62 |
|
|
variable |
|
63 |
|
|
variable |
7.4.8. Relaxation Hint Relocations
These carry no data; they mark sites eligible for relaxation.
Relocation |
Type |
Meaning |
|---|---|---|
|
51 |
Paired with another relocation to indicate the site may be relaxed |
|
43 |
Requests NOP padding to meet an alignment requirement; bytes may be deleted by relaxation |
|
28 |
Marks the |
|
255 |
Vendor extension marker; the following relocation is vendor-defined |
7.5. Internal (Linker-Only) Relocations
These relocations are created by ELD during relaxation and are never present in input object files. They are used to apply the final encoding after an instruction sequence has been rewritten.
Relocation |
Encoding |
Operation |
Inst size |
Notes |
|---|---|---|---|---|
|
|
|
16-bit |
LUI compressed to |
|
|
|
16-bit |
Load immediate compressed to |
|
|
|
32-bit |
GP-relative I-type load/compute |
|
|
|
32-bit |
GP-relative S-type store |
|
|
|
32-bit |
TP-relative I-type after TLS-LE relaxation |
|
|
|
32-bit |
TP-relative S-type after TLS-LE relaxation |
|
none |
— |
16-bit |
Table-jump ( |
|
|
|
48-bit |
Xqcilo absolute load (26-bit imm) |
|
|
|
48-bit |
Xqcilo absolute store (26-bit imm) |
|
|
|
48-bit |
Xqcilo GP-relative load |
|
|
|
48-bit |
Xqcilo GP-relative store |
|
|
|
32-bit |
Xqci 20-bit absolute |
|
|
|
48-bit |
Xqci extended branch |
|
|
|
48-bit |
Xqci extended 32-bit immediate |
|
|
|
48-bit |
Xqci extended call (via PLT) |
|
none |
— |
16-bit |
Xqcilo 16-bit access marker |
|
none |
— |
32-bit |
Xqcilo 32-bit access marker |
7.6. Relaxations
Relaxation is an iterative pass that shrinks instruction sequences when the final link addresses allow it. Each pass may reduce code size; the linker repeats until no further changes occur.
7.6.1. Relaxation Flags
Flag |
Default |
Description |
|---|---|---|
|
enabled |
Master switch; disables all relaxations when off |
|
off |
Disable compressed-instruction relaxations ( |
|
off |
Disable GP-relative relaxations (requires |
|
off |
Disable zero-page relaxations (symbols with value in signed 12-bit range) |
|
off |
Disable GOT load relaxations for non-preemptible symbols |
|
off |
Disable TLS descriptor relaxations |
|
disabled |
Enable Xqci instruction relaxations ( |
|
disabled |
Enable table-jump relaxations ( |
7.6.2. Call Relaxation (R_RISCV_CALL / R_RISCV_CALL_PLT)
An auipc+jalr pair (8 bytes) is shrunk in priority order.
→ c.j / c.jal (−6 bytes): offset fits 12 bits signed, --no-relax-c
not set; c.j when rd=x0, c.jal when rd=x1 (RV32 only).
# Before (8 bytes)
auipc ra, %pcrel_hi(func) # R_RISCV_CALL_PLT
jalr ra, %pcrel_lo(1b)(ra)
# After (2 bytes)
c.jal func # or c.j func when rd=x0
→ cm.jt / cm.jalt (−6 bytes): --relax-tbljal enabled, target has a
valid JVT entry, non-PIC.
# Before (8 bytes)
auipc ra, %pcrel_hi(func) # R_RISCV_CALL_PLT
jalr ra, %pcrel_lo(1b)(ra)
# After (2 bytes)
cm.jalt <jvt_index> # or cm.jt when rd=x0
→ jal (−4 bytes): offset fits 21 bits signed (±1 MiB).
# Before (8 bytes)
auipc ra, %pcrel_hi(func) # R_RISCV_CALL_PLT
jalr ra, %pcrel_lo(1b)(ra)
# After (4 bytes)
jal ra, func # R_RISCV_JAL
→ qc.e.j / qc.e.jal (−2 bytes): --relax-xqci enabled, RV32 only.
# Before (8 bytes)
auipc ra, %pcrel_hi(func)
jalr ra, %pcrel_lo(1b)(ra)
# After (6 bytes)
qc.e.jal func # 48-bit Xqci extended call
7.6.3. JAL Relaxation (R_RISCV_JAL)
→ cm.jt / cm.jalt (−2 bytes): --relax-tbljal enabled, valid JVT
entry.
# Before (4 bytes)
jal ra, func # R_RISCV_JAL
# After (2 bytes)
cm.jalt <jvt_index>
7.6.4. LUI Relaxation (R_RISCV_HI20 + R_RISCV_LO12_*)
A lui+LO12 pair is shrunk in priority order.
→ zero-base (−4 bytes): symbol value fits signed 12 bits; --no-relax-zero
not set.
# Before (8 bytes)
lui a0, %hi(var) # R_RISCV_HI20
addi a0, a0, %lo(var) # R_RISCV_LO12_I
# After (4 bytes)
addi a0, x0, var # immediate encodes full value
→ GP-relative (−4 bytes): symbol within gp ± 2048; --no-relax-gp not
set, non-PIC.
# Before (8 bytes)
lui a0, %hi(var) # R_RISCV_HI20
lw a0, %lo(var)(a0) # R_RISCV_LO12_I
# After (4 bytes)
lw a0, var(gp) # R_RISCV_GPREL_I
→ c.lui (−2 bytes): immediate fits 6-bit non-zero CI field; rd ≠ x0,
rd ≠ x2; --no-relax-c not set.
# Before (4 bytes)
lui a0, %hi(var) # R_RISCV_HI20
# After (2 bytes)
c.lui a0, %hi(var) # R_RISCV_RVC_LUI
7.6.5. PC-Relative to GP Relaxation (R_RISCV_PCREL_HI20 + R_RISCV_PCREL_LO12_I)
→ GP-relative (−4 bytes): symbol within gp ± 2048; --no-relax-gp not
set, non-PIC.
# Before (8 bytes)
auipc a0, %pcrel_hi(var) # R_RISCV_PCREL_HI20
lw a0, %pcrel_lo(1b)(a0) # R_RISCV_PCREL_LO12_I
# After (4 bytes)
lw a0, var(gp) # R_RISCV_GPREL_I
7.6.6. GOT Relaxation (R_RISCV_GOT_HI20 + R_RISCV_PCREL_LO12_I)
For non-preemptible symbols the GOT indirection is bypassed.
→ c.li (−6 bytes): symbol value fits 6-bit signed CI field.
# Before (8 bytes)
auipc a0, %pcrel_hi(sym@GOT) # R_RISCV_GOT_HI20
lw a0, %pcrel_lo(1b)(a0) # R_RISCV_PCREL_LO12_I (GOT load)
# After (2 bytes)
c.li a0, sym # R_RISCV_RVC_LI
→ addi x0, imm (−4 bytes): symbol value fits 12-bit signed.
# Before (8 bytes)
auipc a0, %pcrel_hi(sym@GOT)
lw a0, %pcrel_lo(1b)(a0)
# After (4 bytes)
addi a0, x0, sym # immediate encodes full value
→ auipc + addi PCREL (0 bytes): symbol fits PC-relative 32-bit range;
GOT load converted to direct address calculation.
# Before (8 bytes)
auipc a0, %pcrel_hi(sym@GOT) # R_RISCV_GOT_HI20
lw a0, %pcrel_lo(1b)(a0) # GOT load
# After (8 bytes)
auipc a0, %pcrel_hi(sym) # R_RISCV_PCREL_HI20
addi a0, a0, %pcrel_lo(1b) # R_RISCV_PCREL_LO12_I (no GOT access)
7.6.7. TLSDESC Relaxation
For symbols with local/static TLS, the TLSDESC call sequence is replaced
with a direct TP-relative access. Disabled by --no-relax-tlsdesc.
# Before: GD/TLSDESC (16 bytes, calls runtime)
auipc a0, %tlsdesc_hi(x) # R_RISCV_TLSDESC_HI20
lw t0, %tlsdesc_lo(1b)(a0) # R_RISCV_TLSDESC_LOAD_LO12
addi a0, a0, %tlsdesc_lo(1b) # R_RISCV_TLSDESC_ADD_LO12
jalr t0, t0(a0) # R_RISCV_TLSDESC_CALL
# After: TLS-LE (8–12 bytes, no runtime call)
lui a0, %tprel_hi(x) # R_RISCV_TPREL_HI20 (omitted if fits 12-bit)
add a0, a0, tp # R_RISCV_TPREL_ADD
lw a0, %tprel_lo(x)(a0) # R_RISCV_TPREL_LO12_I
7.6.8. Zcmt / Xqccmt Table-Jump Relaxation (--relax-tbljal)
The Zcmt extension introduces a 64-entry Jump Vector Table (.riscv.jvt),
a 64-byte aligned array of function-pointer-sized words. At link time ELD
builds this table from all call targets that appear more than once, then
replaces each call site with a 2-byte cm.jalt N (return via ra) or
cm.jt N (tail call, no link register written). The index N is the
zero-based slot in the JVT where the target address is stored.
Xqccmt uses the same 16-bit encoding under the names qc.cm.jalt /
qc.cm.jt, and additionally supports writing the return address into t0
(x5) by setting bit 0 in the JVT entry.
.riscv.jvt section layout (4 entries shown, 32-bit target):
.section .riscv.jvt,"a",@progbits
.align 6 # 64-byte alignment (Zcmt requirement)
jvt_base:
.word foo # entry 0 — target of cm.jalt 0 / cm.jt 0
.word bar # entry 1
.word baz # entry 2
.word qux # entry 3
... # up to 64 entries
Call site relaxation (−6 bytes per site, from auipc+jalr to cm.jalt):
# Before (8 bytes): called at many sites throughout the binary
auipc ra, %pcrel_hi(foo) # R_RISCV_CALL_PLT
jalr ra, %pcrel_lo(1b)(ra)
# After (2 bytes): JVT entry 0 holds foo's address
cm.jalt 0 # R_RISCV_TBJAL — loads PC from jvt_base[0], links ra
Tail-call relaxation (−6 bytes, cm.jt when rd=x0):
# Before (8 bytes)
auipc x0, %pcrel_hi(baz)
jalr x0, %pcrel_lo(1b)(x0)
# After (2 bytes): JVT entry 2 holds baz's address
cm.jt 2 # tail call — no link register written
JAL → cm.jalt (−2 bytes):
# Before (4 bytes): already within ±1 MiB but target is in the JVT
jal ra, bar # R_RISCV_JAL
# After (2 bytes)
cm.jalt 1 # R_RISCV_TBJAL
The linker only builds JVT entries for targets where the total bytes saved across all call sites exceeds the 4-byte cost of the JVT entry itself plus any required padding to meet the 64-byte section alignment.
7.6.9. ALIGN Relaxation (R_RISCV_ALIGN)
NOP padding inserted by the assembler to satisfy .align directives is
removed by the linker after relaxation shrinks preceding code. The linker
recalculates required padding and deletes any excess NOP bytes.
# Before (assembler-inserted NOPs, e.g. .align 4 after a 2-byte instruction)
c.j target # 2 bytes
nop # 2 bytes — R_RISCV_ALIGN padding
nop # 2 bytes — R_RISCV_ALIGN padding
aligned_label: # now at 4-byte boundary
# After (if relaxation has already re-aligned the section)
c.j target # 2 bytes
nop # 2 bytes — only one NOP needed
aligned_label:
7.7. Common Instruction Sequences
7.7.1. Absolute Address (HI20 + LO12)
lui a0, %hi(symbol) # R_RISCV_HI20
addi a0, a0, %lo(symbol) # R_RISCV_LO12_I
7.7.2. PC-Relative Address (AUIPC + LO12)
auipc a0, %pcrel_hi(symbol) # R_RISCV_PCREL_HI20
addi a0, a0, %pcrel_lo(1b) # R_RISCV_PCREL_LO12_I
7.7.3. Function Call (CALL → relaxed to JAL or compressed)
auipc ra, %pcrel_hi(func) # R_RISCV_CALL / R_RISCV_CALL_PLT
jalr ra, %pcrel_lo(1b)(ra) # (paired with auipc)
After relaxation (if target within ±1 MiB):
jal ra, func # R_RISCV_JAL
7.7.4. TLS Local-Exec (TPREL)
lui a0, %tprel_hi(x) # R_RISCV_TPREL_HI20
add a0, a0, tp, %tprel_add(x) # R_RISCV_TPREL_ADD
lw a0, %tprel_lo(x)(a0) # R_RISCV_TPREL_LO12_I
7.8. References
RISC-V ELF psABI specification: https://github.com/riscv-non-isa/riscv-elf-psabi-doc/blob/master/riscv-elf.adoc
RISC-V ISA specification: https://github.com/riscv/riscv-isa-manual
RISC-V Compressed extension (RVC) — Volume I, Chapter 16
Zcmt table-jump extension: https://github.com/riscv/riscv-isa-manual