Introduction
In control flow operations—such as jumps (jmp), branch instructions, and function calls (call)—the distinction between CISC and RISC architectures becomes immediately visible.
In a variable-length CISC architecture like x86, the processor can encode absolute 32-bit addresses directly into jump instructions. Conversely, a fixed-length 32-bit RISC architecture like RISC-V (RV32I) must fit every instruction, including its opcode, target registers, and offset payload, into exactly 4 bytes (32 bits).
This constraint introduces a fundamental challenge: How do you execute a long jump or function call to an arbitrary address in a 32-bit memory space when instructions are limited to 32 bits?
How x86_32 Handles Jumps and Calls
On x86_32, jumps and calls can encode either relative or absolute 32-bit offsets directly inside the instruction stream.
.intel_syntax noprefix
.section .text
.global _start
_start:
call 0x12345678 # Call a function located at address 0x12345678
jmp 0x87654321 # Absolute / Far Jump to 0x87654321
When assembled and disassembled, x86 produces single multi-byte instructions:
08049000 <_start>:
8049000: e8 73 66 2f 0a call 12345678 <_start+0x0a2f6678>
8049005: ea 21 43 65 87 1a 00 jmp 001a:87654321
The x86 architecture seamlessly embeds the full target address or relative offset into 5-byte to 7-byte instruction encodings.
How RISC-V (RV32I) Handles Long Jumps and Calls
Because an RV32I instruction cannot exceed 32 bits, a single instruction cannot encode a full 32-bit target address.
To solve this, RISC-V provides two primary hardware instructions for jumps:
-
jal(Jump and Link, J-Type format): Uses a 20-bit immediate field (sign-extended and shifted left by 1 bit), allowing relative jumps within a ±1 MiB range around the current program counter (pc). -
jalr(Jump and Link Register, I-Type format): Adds a 12-bit signed immediate offset to a base register.
When a jump target exceeds the ±1 MiB reach of a single jal, the RISC-V assembler converts pseudo-instructions like call or tail into a two-instruction pair.
.section .text
.global _start
_start:
call target_func # Pseudo-instruction for function call
If target_func is located beyond the 1 MiB limit (e.g., at address 0x12345678), the assembler expands call into the following sequence:
00010074 <_start>:
10074: 123450e7 auipc ra, 0x12345
10078: 6780e0e7 jalr ra, 1656(ra) # 1656 = 0x678
Understanding auipc + `jalr`
The combined sequence works through two distinct steps:
-
auipc ra, 0x12345(Add Upper Immediate to PC): Adds the upper 20 bits shifted left by 12 bits to the current value of the program counter (pc) and stores the result in registerra(Return Address). -
jalr ra, 1656(ra)(Jump and Link Register): Adds the remaining sign-extended 12-bit offset (0x678= 1656) tora, savespc + 4intora, and jumps to the calculated target.
This combination allows RISC-V to execute PC-relative calls anywhere within a ±2 GiB address space using two 32-bit instructions (8 bytes in total).
The Sign-Extension Trap on Relocations
Just like loading constants with lui + addi, auipc + jalr is vulnerable to sign-extension issues.
If bit 11 of the 12-bit offset in jalr is 1 (i.e., the offset is negative), jalr will subtract from the address pre-calculated by auipc. To prevent incorrect branching, the linker or assembler must automatically compensate by adding 1 to the 20-bit upper immediate passed to auipc.
Architectural Comparison
| Feature | CISC (x86_32) | RISC (RISC-V RV32I) |
|---|---|---|
| Max Reach of Single Jump | Full 32-bit address space (4 GiB) | ±1 MiB (jal relative) |
| Long Call Sequence | Single call instruction (5 bytes) |
auipc + jalr pair (8 bytes total) |
| Relocation Style | Absolute or PC-relative | Fully PC-relative (Position Independent Code by design) |
| Hardware Impact | Variable-length instruction decoding | Uniform 32-bit pipeline fetching and simplified branching logic |
Epilogue
While x86 relies on complex decoding hardware to handle multi-byte branch targets inline, RISC-V enforces strict structural simplicity. By coupling auipc and jalr, RISC-V achieves fully position-independent control flow across the entire memory space without requiring complex multi-length instruction decoders.
Top comments (0)