RV32I vs x86_32: How Branching Works Across Architectural Lines
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)…
The fundamental difference between CISC and RISC architectures becomes apparent when examining branching operations like jumps and calls. In x86, a variable-length architecture, the processor can include absolute 32-bit addresses directly within jump instructions. On the other hand, fixed-length RISC-V (RV32I) must fit all instruction components—including opcode, target registers, and offset payload—into a precisely 32-bit size.
This limitation presents a significant challenge: how to execute long jumps and function calls in a 32-bit memory space when each instruction is capped at 32 bits. In x86_32, jumps and calls accommodate either relative or absolute 32-bit offsets directly within the instruction sequence. For example, the assembly code snippet `_start: call 0x12345678` results in a single instruction encoding `e8 73 66 2f 0a`, encoded as a call to address `0x12345678`.
Conversely, RISC-V's RV32I lacks the capacity to encode a full 32-bit target address within a single instruction. To circumvent this, RISC-V provides two primary jump instructions: jal (Jump and Link) and jalr (Jump and Link Register). The jal instruction utilizes a 20-bit immediate field for relative jumps, allowing jumps within a ±1 MiB range around the current program counter (PC).
However, for calls exceeding this limit, the assembler converts pseudo-instructions like call into a pair of instructions, typically `auipc` followed by `jalr`. The `auipc` instruction adds an upper 20-bit immediate shifted left by 12 bits to the current program counter (PC) and stores the result in a designated register (usually `ra`), representing the return address.
The `jalr` instruction then adds an 12-bit signed offset to the value in `ra`, adjusts the program counter accordingly, and performs the jump. This sequence, `auipc ra, 0x12345` followed by `jalr ra, 1656(ra)`, enables RISC-V to handle long jumps and calls across a ±2 GiB address space using two 32-bit instructions (8 bytes total).
However, this approach requires careful management of sign-extension issues. If the signed 12-bit offset in `jalr` is negative, the resulting address may be incorrectly calculated. To avoid this, the assembler automatically compensates by adding 1 to the upper 20-bit immediate field passed to `auipc`, ensuring correct branching.
In summary, while x86_32 relies on complex decoding hardware to manage multi-byte branch targets inline, RISC-V mandates a more straightforward, uniform approach by enforcing strict structural simplicity. Through the judicious use of `auipc` and `jalr`, RISC-V achieves fully position-independent control flow across the entire memory space without the need for intricate multi-length instruction decoders.
Written by urgent.news from Dev.to's reporting — not their text. Machine-written — may contain errors; check the original before relying on it.