Backend
RV32I vs x86_32: How Branching Works Across Architectural Lines
ddupard DEV Community
1 views
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 register ra (Return Address).
jalr ra, 1656(ra) (Jump and Link Register): Adds the remaining sign-extended 12-bit offset (0x678 = 1656) to ra, saves pc + 4 into ra, 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.
Read original: https://dev.to/ddupard/rv32i-vs-x8632-how-branching-works-across-architectural-lines-58l3
← Previous
Ten fine-tuning mistakes I see students make (and made myself)
Next →
Running ML models in the browser with ONNX Runtime Web: a practical guide
Related
Comments0
No comments yet — be the first