# RISC-V Relocation Reference ## 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 --- ## Relocation Conventions | Symbol | Meaning | |--------|---------| | `S` | Runtime address of the referenced symbol | | `A` | Relocation addend | | `P` | Address of the relocation site | | `G` | Address of the global pointer (`__global_pointer$`) | | `GOT` | Global Offset Table base address | | `X` | Relocation result before masking/encoding | --- ## 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`. ### Unscrambled (linear) encodings | Encoding | Instruction format | Value bits → instruction bits | Width | Inst size | |----------------|--------------------------|----------------------------------------|-------|-----------| | `EncTy_None` | No field written | — | — | — | | `EncTy_8` | Data byte | `X[7:0]` → `inst[7:0]` | 8 | 8-bit | | `EncTy_16` | Data halfword | `X[15:0]` → `inst[15:0]` | 16 | 16-bit | | `EncTy_32` | Data word | `X[31:0]` → `inst[31:0]` | 32 | 32-bit | | `EncTy_64` | Data doubleword | `X[63:0]` → `inst[63:0]` | 64 | 64-bit | | `EncTy_6` | Byte, low 6 bits | `X[5:0]` → `inst[5:0]` | 6 | 8-bit | | `EncTy_LEB128` | ULEB128 data | Variable-length unsigned LEB128 | var | variable | ### 32-bit instruction encodings | Encoding | Instruction format | Value bits → instruction bits | Width | |-----------------|------------------------------|-----------------------------------------------------------------------------------------------|-------| | `EncTy_I` | I-type (`addi`, `lw`, …) | `X[11:0]` → `inst[31:20]` | 12 | | `EncTy_S` | S-type (`sw`, `sb`, …) | `X[11:5]` → `inst[31:25]`
`X[4:0]` → `inst[11:7]` | 12 | | `EncTy_SB` | B-type (branch) | `X[12]` → `inst[31]`
`X[10:5]` → `inst[30:25]`
`X[4:1]` → `inst[11:8]`
`X[11]` → `inst[7]` | 13 (signed, 2-byte aligned) | | `EncTy_UJ` | J-type (`jal`) | `X[20]` → `inst[31]`
`X[10:1]` → `inst[30:21]`
`X[11]` → `inst[20]`
`X[19:12]` → `inst[19:12]` | 21 (signed, 2-byte aligned) | | `EncTy_U_HI20` | U-type (`lui`, `auipc`) | `(X + 0x800)[31:12]` → `inst[31:12]` (carry-adjusted upper 20 bits) | 20 | | `EncTy_U_ABS20` | Xqci 20-bit U-type | `X[19]` → `inst[31]`
`X[14:0]` → `inst[30:16]`
`X[18:15]` → `inst[15:12]` | 20 | > **`EncTy_U_HI20` carry adjustment:** `0x800` is added before masking so that > when the low 12-bit `addi` immediate is sign-extended, it correctly > reconstructs the original address together with the upper 20 bits. ### 16-bit (RVC) instruction encodings | Encoding | Instruction format | Value bits → instruction bits | Width | |-----------|----------------------------|-------------------------------------------------------------------------------------------------------------------------------------|-------| | `EncTy_CB` | C.B-type (RVC branch) | `X[8]` → `inst[12]`
`X[4:3]` → `inst[11:10]`
`X[7:6]` → `inst[6:5]`
`X[2:1]` → `inst[4:3]`
`X[5]` → `inst[2]` | 9 (signed, 2-byte aligned) | | `EncTy_CJ` | C.J-type (RVC jump) | `X[11]` → `inst[12]`
`X[4]` → `inst[11]`
`X[9:8]` → `inst[10:9]`
`X[10]` → `inst[8]`
`X[6]` → `inst[7]`
`X[7]` → `inst[6]`
`X[3:1]` → `inst[5:3]`
`X[5]` → `inst[2]` | 12 (signed, 2-byte aligned) | | `EncTy_CI` | C.I-type (`c.lui`, `c.li`) | `X[5+shift]` → `inst[12]`
`X[4+shift:shift]` → `inst[6:2]`
(shift=12 for `c.lui`, shift=0 for `c.li`) | 6 | ### 48-bit (Xqci/Xqcilo) instruction encodings | Encoding | Instruction format | Value bits → instruction bits | Width | |---------------|---------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------|-------| | `EncTy_QC_EI` | Xqcilo E-I-type load | `X[9:0]` → `inst[29:20]`
`X[25:10]` → `inst[47:32]` | 26 (signed) | | `EncTy_QC_ES` | Xqcilo E-S-type store | `X[4:0]` → `inst[11:7]`
`X[9:5]` → `inst[31:25]`
`X[25:10]` → `inst[47:32]` | 26 (signed) | | `EncTy_QC_EB` | Xqci E-B-type branch | same scramble as `EncTy_SB` in `inst[31:0]` (the low 32 bits of the 48-bit instruction) | 13 (signed, 2-byte aligned) | | `EncTy_QC_EJ` | Xqci E-J-type call | `X[31:16]` → `inst[63:48]`
`X[12]` → `inst[31]`
`X[10:5]` → `inst[30:25]`
`X[15:13]` → `inst[20:17]`
`X[4:1]` → `inst[11:8]`
`X[11]` → `inst[7]` | 32 (signed) | | `EncTy_QC_EAI`| Xqci E-AI-type absolute imm | `X[31:0]` → `inst[47:16]` | 32 (signed) | --- ## Standard Relocations ### Dynamic / Runtime Relocations These are written by the dynamic linker at load time. | Relocation | Type | Encoding | Operation | Size | |------------|------|----------|-----------|------| | `R_RISCV_NONE` | 0 | none | — | — | | `R_RISCV_32` | 1 | `EncTy_32` | `S + A` | 32 | | `R_RISCV_64` | 2 | `EncTy_64` | `S + A` | 64 | | `R_RISCV_RELATIVE` | 3 | none | `B + A` | 32 | | `R_RISCV_COPY` | 4 | none | Copy from shared object | 32 | | `R_RISCV_JUMP_SLOT` | 5 | none | PLT entry | 32 | | `R_RISCV_TLS_DTPMOD32` | 6 | none | TLS module index (32-bit) | 32 | | `R_RISCV_TLS_DTPMOD64` | 7 | none | TLS module index (64-bit) | 64 | | `R_RISCV_TLS_DTPREL32` | 8 | `EncTy_32` | TLS DTP offset (32-bit) | 32 | | `R_RISCV_TLS_DTPREL64` | 9 | `EncTy_64` | TLS DTP offset (64-bit) | 64 | | `R_RISCV_TLS_TPREL32` | 10 | `EncTy_32` | TLS TP offset (32-bit) | 32 | | `R_RISCV_TLS_TPREL64` | 11 | `EncTy_64` | TLS TP offset (64-bit) | 64 | ### Control Flow Relocations | Relocation | Type | Encoding | Operation | Inst size | Alignment | Range check | |------------|------|----------|-----------|-----------|-----------|-------------| | `R_RISCV_BRANCH` | 12 | `EncTy_SB` | `S + A - P` | 32-bit | 2 bytes | `−4096 <= X < 4096`, `X` must be even | | `R_RISCV_JAL` | 13 | `EncTy_UJ` | `S + A - P` | 32-bit | 2 bytes | `−1048576 <= X < 1048576`, `X` must be even | | `R_RISCV_CALL` | 14 | `EncTy_None` | `S + A - P` | 32-bit | 2 bytes | none (32-bit range via `auipc+jalr`) | | `R_RISCV_CALL_PLT` | 15 | `EncTy_None` | `S + A - P` (via PLT) | 32-bit | 2 bytes | none (32-bit range via `auipc+jalr`) | | `R_RISCV_RVC_BRANCH` | 44 | `EncTy_CB` | `S + A - P` | 16-bit | 2 bytes | `−256 <= X < 256`, `X` must be even | | `R_RISCV_RVC_JUMP` | 45 | `EncTy_CJ` | `S + A - P` | 16-bit | 2 bytes | `−2048 <= X < 2048`, `X` must be even | > **`R_RISCV_CALL` / `R_RISCV_CALL_PLT`:** Applied to an `auipc`+`jalr` pair > (8 bytes). The linker splits `X` into a 20-bit upper part (encoded in `auipc`) > and a 12-bit signed lower part (encoded in `jalr`), with a carry adjustment > when bit 11 of the lower part is set. These relocations are the primary > candidates for call relaxation (see [Relaxations](#relaxations)). ### 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 | |------------|------|----------|-----------|-----------|-------------| | `R_RISCV_PCREL_HI20` | 19 | `EncTy_U_HI20` | `S + A - P` | 32-bit | none | | `R_RISCV_PCREL_LO12_I` | 20 | `EncTy_I` | low 12 bits of paired HI20 | 32-bit | none | | `R_RISCV_PCREL_LO12_S` | 21 | `EncTy_S` | low 12 bits of paired HI20 (stores) | 32-bit | none | > **Pair invariant:** The symbol of `R_RISCV_PCREL_LO12_I/S` must point to > the address of the corresponding `R_RISCV_PCREL_HI20` relocation site, not > the target symbol. The linker resolves the HI20 first, then propagates its > computed value to the LO12. ### Absolute Address Relocations | Relocation | Type | Encoding | Operation | Inst size | Range check | |------------|------|----------|-----------|-----------|-------------| | `R_RISCV_HI20` | 22 | `EncTy_U_HI20` | `S + A` | 32-bit | `−2^31 <= X < 2^31` (32-bit range) | | `R_RISCV_LO12_I` | 23 | `EncTy_I` | `S + A` low 12 bits | 32-bit | none | | `R_RISCV_LO12_S` | 24 | `EncTy_S` | `S + A` low 12 bits (stores) | 32-bit | none | ### TLS Relocations | Relocation | Type | Encoding | Operation | Inst size | |------------|------|----------|-----------|-----------| | `R_RISCV_TPREL_HI20` | 25 | `EncTy_U_HI20` | `S + A - tp` | 32-bit | | `R_RISCV_TPREL_LO12_I` | 26 | `EncTy_I` | low 12 bits of TP offset | 32-bit | | `R_RISCV_TPREL_LO12_S` | 27 | `EncTy_S` | low 12 bits of TP offset (stores) | 32-bit | | `R_RISCV_TPREL_ADD` | 28 | none | Relaxation hint (no bits written) | 32-bit | | `R_RISCV_TLS_GOT_HI20` | 17 | `EncTy_U_HI20` | `GOT(S) - P` | 32-bit | | `R_RISCV_TLS_GD_HI20` | 18 | `EncTy_U_HI20` | `GOT(S) - P` (GD model) | 32-bit | | `R_RISCV_TLSDESC_HI20` | 58 | `EncTy_U_HI20` | `TLSDESC(S) - P` | 32-bit | | `R_RISCV_TLSDESC_LOAD_LO12` | 59 | `EncTy_I` | low 12 bits of TLSDESC | 32-bit | | `R_RISCV_TLSDESC_ADD_LO12` | 60 | `EncTy_I` | low 12 bits of TLSDESC | 32-bit | | `R_RISCV_TLSDESC_CALL` | 61 | none | TLSDESC call hint | 32-bit | ### GOT Relocations | Relocation | Type | Encoding | Operation | Inst size | |------------|------|----------|-----------|-----------| | `R_RISCV_GOT_HI20` | 16 | `EncTy_U_HI20` | `GOT(S) - P` | 32-bit | | `R_RISCV_GOT32_PCREL` | 37 | `EncTy_None` | `GOT(S) - P` (32-bit) | 32-bit | ### Arithmetic / Addend Relocations Used in debug sections and compact unwind tables to encode label differences. | Relocation | Type | Encoding | Operation | Size | |------------|------|----------|-----------|------| | `R_RISCV_ADD8` | 29 | `EncTy_8` | `*(loc) += S + A` | 8 | | `R_RISCV_ADD16` | 30 | `EncTy_16` | `*(loc) += S + A` | 16 | | `R_RISCV_ADD32` | 31 | `EncTy_32` | `*(loc) += S + A` | 32 | | `R_RISCV_ADD64` | 32 | `EncTy_64` | `*(loc) += S + A` | 64 | | `R_RISCV_SUB8` | 33 | `EncTy_8` | `*(loc) -= S + A` | 8 | | `R_RISCV_SUB16` | 34 | `EncTy_16` | `*(loc) -= S + A` | 16 | | `R_RISCV_SUB32` | 35 | `EncTy_32` | `*(loc) -= S + A` | 32 | | `R_RISCV_SUB64` | 36 | `EncTy_64` | `*(loc) -= S + A` | 64 | | `R_RISCV_SUB6` | 52 | `EncTy_6` | `*(loc)[5:0] -= S + A` | 8 | | `R_RISCV_SET6` | 53 | `EncTy_6` | `*(loc)[5:0] = S + A` | 8 | | `R_RISCV_SET8` | 54 | `EncTy_8` | `*(loc) = S + A` | 8 | | `R_RISCV_SET16` | 55 | `EncTy_16` | `*(loc) = S + A` | 16 | | `R_RISCV_SET32` | 56 | `EncTy_32` | `*(loc) = S + A` | 32 | | `R_RISCV_32_PCREL` | 57 | `EncTy_32` | `S + A - P` (32-bit) | 32 | | `R_RISCV_SET_ULEB128` | 62 | `EncTy_LEB128` | `*(loc) = S + A` (ULEB128) | variable | | `R_RISCV_SUB_ULEB128` | 63 | `EncTy_LEB128` | `*(loc) -= S + A` (ULEB128) | variable | ### Relaxation Hint Relocations These carry no data; they mark sites eligible for relaxation. | Relocation | Type | Meaning | |------------|------|---------| | `R_RISCV_RELAX` | 51 | Paired with another relocation to indicate the site may be relaxed | | `R_RISCV_ALIGN` | 43 | Requests NOP padding to meet an alignment requirement; bytes may be deleted by relaxation | | `R_RISCV_TPREL_ADD` | 28 | Marks the `add` in a `TPREL_HI20`+`add`+`TPREL_LO12` TLS-LE sequence; deleted during relaxation | | `R_RISCV_VENDOR` | 255 | Vendor extension marker; the following relocation is vendor-defined | --- ## 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 | |------------|----------|-----------|-----------|-------| | `R_RISCV_RVC_LUI` | `EncTy_CI` | `S` | 16-bit | LUI compressed to `c.lui` | | `R_RISCV_RVC_LI` | `EncTy_CI` | `S` | 16-bit | Load immediate compressed to `c.li` | | `R_RISCV_GPREL_I` | `EncTy_I` | `S - G` | 32-bit | GP-relative I-type load/compute | | `R_RISCV_GPREL_S` | `EncTy_S` | `S - G` | 32-bit | GP-relative S-type store | | `R_RISCV_TPREL_I` | `EncTy_I` | `S - tp` | 32-bit | TP-relative I-type after TLS-LE relaxation | | `R_RISCV_TPREL_S` | `EncTy_S` | `S - tp` | 32-bit | TP-relative S-type after TLS-LE relaxation | | `R_RISCV_TBJAL` | none | — | 16-bit | Table-jump (`cm.jt`/`cm.jalt`) after call relaxation | | `R_RISCV_QC_ABS26_I` | `EncTy_QC_EI` | `S + A` | 48-bit | Xqcilo absolute load (26-bit imm) | | `R_RISCV_QC_ABS26_S` | `EncTy_QC_ES` | `S + A` | 48-bit | Xqcilo absolute store (26-bit imm) | | `R_RISCV_QC_GPREL26_I` | `EncTy_QC_EI` | `S - G` | 48-bit | Xqcilo GP-relative load | | `R_RISCV_QC_GPREL26_S` | `EncTy_QC_ES` | `S - G` | 48-bit | Xqcilo GP-relative store | | `R_RISCV_QC_ABS20_U` | `EncTy_U_ABS20` | `S + A` | 32-bit | Xqci 20-bit absolute | | `R_RISCV_QC_E_BRANCH` | `EncTy_QC_EB` | `S + A - P` | 48-bit | Xqci extended branch | | `R_RISCV_QC_E_32` | `EncTy_QC_EAI` | `S + A` | 48-bit | Xqci extended 32-bit immediate | | `R_RISCV_QC_E_CALL_PLT` | `EncTy_QC_EJ` | `S + A - P` | 48-bit | Xqci extended call (via PLT) | | `R_RISCV_QC_ACCESS_16` | none | — | 16-bit | Xqcilo 16-bit access marker | | `R_RISCV_QC_ACCESS_32` | none | — | 32-bit | Xqcilo 32-bit access marker | --- ## 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. ### Relaxation Flags | Flag | Default | Description | |------|---------|-------------| | `--relax` / `--no-relax` | **enabled** | Master switch; disables all relaxations when off | | `--no-relax-c` | off | Disable compressed-instruction relaxations (`c.j`, `c.jal`, `c.lui`, `c.li`) | | `--no-relax-gp` | off | Disable GP-relative relaxations (requires `__global_pointer$`, non-PIC only) | | `--no-relax-zero` | off | Disable zero-page relaxations (symbols with value in signed 12-bit range) | | `--no-relax-got` | off | Disable GOT load relaxations for non-preemptible symbols | | `--no-relax-tlsdesc` | off | Disable TLS descriptor relaxations | | `--relax-xqci` / `--no-relax-xqci` | **disabled** | Enable Xqci instruction relaxations (`qc.e.j`, `qc.e.jal`) | | `--relax-tbljal[=zcmt\|xqccmt]` / `--no-relax-tbljal` | **disabled** | Enable table-jump relaxations (`cm.jt`/`cm.jalt`) | ### 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). ```asm # 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. ```asm # Before (8 bytes) auipc ra, %pcrel_hi(func) # R_RISCV_CALL_PLT jalr ra, %pcrel_lo(1b)(ra) # After (2 bytes) cm.jalt # or cm.jt when rd=x0 ``` **→ `jal` (−4 bytes):** offset fits 21 bits signed (±1 MiB). ```asm # 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. ```asm # 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 ``` ### JAL Relaxation (`R_RISCV_JAL`) **→ `cm.jt` / `cm.jalt` (−2 bytes):** `--relax-tbljal` enabled, valid JVT entry. ```asm # Before (4 bytes) jal ra, func # R_RISCV_JAL # After (2 bytes) cm.jalt ``` ### 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. ```asm # 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. ```asm # 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. ```asm # Before (4 bytes) lui a0, %hi(var) # R_RISCV_HI20 # After (2 bytes) c.lui a0, %hi(var) # R_RISCV_RVC_LUI ``` ### 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. ```asm # 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 ``` ### 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. ```asm # 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. ```asm # 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. ```asm # 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) ``` ### 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`. ```asm # 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 ``` ### 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`):** ```asm # 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`):** ```asm # 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):** ```asm # 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. ### 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. ```asm # 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: ``` --- ## Common Instruction Sequences ### Absolute Address (HI20 + LO12) ```asm lui a0, %hi(symbol) # R_RISCV_HI20 addi a0, a0, %lo(symbol) # R_RISCV_LO12_I ``` ### PC-Relative Address (AUIPC + LO12) ```asm auipc a0, %pcrel_hi(symbol) # R_RISCV_PCREL_HI20 addi a0, a0, %pcrel_lo(1b) # R_RISCV_PCREL_LO12_I ``` ### Function Call (CALL → relaxed to JAL or compressed) ```asm 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): ```asm jal ra, func # R_RISCV_JAL ``` ### TLS Local-Exec (TPREL) ```asm 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 ``` --- ## 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