Addressing Modes Side-by-Side
August 3, 2026·22 min read·intermediate
Every instruction that touches memory must answer a fundamental question: where is the data? The answer is an addressing mode, and the set of addressing modes an ISA offers shapes everything from compiler code…
Every instruction that touches memory must answer a fundamental question: where is the data? The answer is an addressing mode, and the set of addressing modes an ISA offers shapes everything from compiler code generation to hardware decoder complexity. Consider loading a 32-bit integer from an array. On RISC-V, the compiler emits lw a0, 0(a1), specifying the address as a base register (a1) plus a 12-bit signed offset (0). On AArch64, the same load might use LDR W0, [X1, X2, LSL #2], which adds X1 to X2 shifted left by two, computing the array address in a single instruction. On x86-64, the equivalent is mov eax, [rbx + rcx*4], using a base register, an index register, and a scale factor all encoded in the SIB byte described in Chapter 17.
Each of these three ISAs provides a different menu of addressing modes, and the differences reflect deep design choices about encoding simplicity, hardware cost, and compiler convenience. This chapter lays the modes side by side, explains how each ISA encodes them, and examines the tradeoffs that each choice creates.
01.A Taxonomy of Addressing Modes
Before comparing ISAs, it helps to have a uniform vocabulary. The following classification covers the modes that appear across RISC-V, AArch64, and x86-64. Not every ISA supports every mode.
The simplest ISAs support only modes 1 through 3 and mode 7. The most complex support all nine. The number of modes an ISA offers is one of the clearest markers on the RISC-versus-CISC spectrum described in Chapter 14.
02.Immediate Operands
An immediate is a constant encoded in the instruction itself. Every ISA provides some form of immediate operand for arithmetic, logical, and comparison instructions. The differences lie in the width of the immediate field and the mechanisms available when the desired constant does not fit.
RISC-V immediates
RISC-V encodes immediates in several fixed formats. I-type instructions carry a 12-bit sign-extended immediate, giving a range of to . S-type (store) instructions split the same 12-bit immediate across two non-contiguous fields. U-type instructions (LUI and AUIPC) carry a 20-bit immediate that fills bits 31 down to 12 of a 32-bit value, with the lower 12 bits zeroed.
To load an arbitrary 32-bit constant, the compiler pairs LUI (which sets the upper 20 bits) with an ADDI (which fills the lower 12 bits). The two-instruction sequence is a direct consequence of the fixed 32-bit instruction length: there is no room for a 32-bit immediate inside a single 32-bit instruction. Chapter 15 discussed this tradeoff in detail.
AArch64 immediates
AArch64 provides a 12-bit unsigned immediate for arithmetic instructions, optionally shifted left by 12 bits. The shifted form allows common page-aligned constants to be expressed in one instruction. Logical instructions use a separate “bitmask immediate” encoding that can represent any repeating bit pattern of length 2, 4, 8, 16, 32, or 64, covering a wide range of masks and bit-field constants that would otherwise require two instructions.
The MOVZ, MOVK, and MOVN instructions build arbitrary 64-bit constants by moving, inserting, or negating 16-bit halfword chunks. A 64-bit constant with only one non-zero halfword takes one instruction. A fully arbitrary 64-bit constant takes up to four instructions.
x86-64 immediates
x86-64 allows 8-bit, 16-bit, and 32-bit immediates encoded directly after the opcode and ModR/M bytes. In 64-bit mode, the MOV instruction has a special encoding that accepts a full 64-bit immediate (opcode B8+rd, 10 bytes total with REX.W). No other x86-64 instruction takes a 64-bit immediate. All other ALU instructions sign-extend a 32-bit immediate to 64 bits, covering the range to .
The variable-length encoding gives x86 more flexibility in immediate width than the fixed-length RISC ISAs, but at the cost of decoder complexity: the decoder must determine the immediate width from the opcode and prefix context, not from a fixed bit position.
03.Register Addressing
Register addressing is the simplest mode: the operand is the contents of a register named in the instruction. Every ISA supports register addressing, and it is the default mode for ALU operations on all three ISAs.
On RISC-V, the R-type format dedicates three 5-bit fields (rd, rs1, rs2) to name any of the 32 GPRs. On AArch64, three 5-bit fields address any of the 31 GPRs plus the zero register or stack pointer. On x86-64, the 3-bit reg and r/m fields in the ModR/M byte (extended to 4 bits by REX) address the 16 GPRs.
The practical consequence is that RISC-V and AArch64 can name three registers per instruction using 15 bits, while x86-64 needs the REX prefix to address more than eight registers and is limited to two explicitly named operands per instruction (the VEX and EVEX prefixes extend this to three or four for SIMD instructions, as discussed in Chapter 17).
04.Base-Plus-Offset Addressing
Base-plus-offset (also called base-plus-displacement) is the workhorse addressing mode for memory access on all three ISAs. The effective address is the sum of a base register and a constant offset encoded in the instruction:
RISC-V base + offset
RISC-V load instructions use the I-type format: lw rd, imm(rs1). The 12-bit signed offset gives a range of 2 KB around the base register. Store instructions use the S-type format with the same effective-address computation but the immediate split across two fields. The narrow offset is sufficient for most stack accesses (local variables and spilled registers sit within a few hundred bytes of the stack pointer) and for structure field accesses (most structures are small). When the offset is insufficient, the compiler inserts an ADDI to adjust the base first.
AArch64 base + offset
AArch64 load and store instructions accept a 12-bit unsigned immediate that is scaled by the access size: the offset is multiplied by 4 for 32-bit loads, by 8 for 64-bit loads. The scaling extends the reach to 16 KB above the base register for word loads and 32 KB above it for double-word loads without widening the offset field. Because the field is unsigned, this form cannot express a negative offset. An alternative encoding accepts a 9-bit signed unscaled offset, which covers negative displacements and byte-granularity access.
x86-64 base + displacement
x86-64 encodes the base register in the ModR/M r/m field and the displacement as 0, 8, or 32 bits selected by the mod field. The 8-bit displacement covers offsets of to , sufficient for most stack-frame accesses. The 32-bit displacement covers 2 GB. The variable displacement width means that instructions accessing nearby data are shorter than those accessing distant data, improving average code density.
05.Indexed and Scaled-Indexed Addressing
Indexed addressing adds a second register to the address computation: . When the index register is multiplied by a constant scale factor, the mode becomes scaled-indexed: .
x86-64 SIB addressing
The fullest expression of scaled-indexed addressing in any mainstream ISA is the x86-64 SIB byte, which encodes base + index scale + displacement with scale (the equation above in Chapter 17). This mode maps directly to C array access: for an array of 8-byte elements at base address arr, the element at index i lives at arr + i * 8. The SIB byte eliminates the shift instruction that RISC-V would need to scale the index.
AArch64 register-offset addressing
AArch64 supports a register-offset mode: LDR X0, [X1, X2, LSL #3], where X2 is shifted left by the log of the access size before being added to X1. This achieves the same effect as x86-64’s SIB addressing but through the barrel shifter on the address computation path. The shift amount is restricted to 0 or the log of the element size (0, 1, 2, or 3 for byte, halfword, word, and doubleword), which covers the common array-access case without a fully general scale factor.
AArch64 also supports an SXTW (sign-extend word) or UXTW (zero-extend word) modifier on the index register, allowing a 32-bit array index to be used directly as the offset without a separate sign-extension instruction.
RISC-V: no indexed addressing
RISC-V deliberately omits indexed addressing from the base integer ISA. The only load/store address form is base + 12-bit immediate. To access arr[i], the compiler must compute base + i * element_size in a separate instruction sequence (typically a shift followed by an add) and then use the result as the base register for a load with offset zero.
This design simplifies the address-generation hardware: the AGU needs only one adder (register + sign-extended immediate), not a second register read port and a shifter. The tradeoff is a higher dynamic instruction count for array-intensive code. The RISC-V architects judged that the hardware simplification was worth the extra instructions, especially in area-constrained embedded cores where every multiplexer and read port costs power [1].
06.PC-Relative Addressing
PC-relative addressing computes the effective address as the program counter plus a signed displacement. Its primary use is position-independent code (PIC): if every data and code reference is expressed relative to the current instruction’s address, the entire program can be loaded at any base address without relocation fixups.
RISC-V AUIPC and PC-relative branches
RISC-V provides AUIPC (Add Upper Immediate to PC), which loads the PC plus a 20-bit upper immediate into a register. The compiler then adds a 12-bit offset with ADDI or LW to reach any address within 2 GB of the current instruction. Branch instructions (B-type) encode a 13-bit signed offset (12 bits shifted left by 1, giving 2-byte alignment), and JAL encodes a 21-bit signed offset.
AArch64 ADRP and ADR
AArch64 provides ADRP (form PC-relative address to 4 KB page) and ADR (form PC-relative address with 21-bit offset). The typical pattern for a global-variable access is ADRP X0, symbol followed by LDR X1, [X0, #:lo12:symbol], reaching any address within 4 GB of the current PC.
x86-64 RIP-relative addressing
x86-64 made RIP-relative addressing the default for memory operands in 64-bit mode. When the ModR/M byte has mod=00 and r/m=101, the effective address is RIP plus a 32-bit signed displacement. The System V AMD64 ABI requires position-independent code in shared libraries, and RIP-relative addressing makes this efficient: every global data reference is a single instruction with no relocation at load time. The 32-bit displacement limits the reachable range to 2 GB from the instruction, which is sufficient for most executables linked with the small or medium code models.
07.ARM Pre-Indexed and Post-Indexed Addressing
AArch64 provides two addressing modes that update the base register as a side effect of the load or store. Neither RISC-V nor x86-64 offers an equivalent.
Pre-indexed addressing
In pre-indexed mode, the base register is updated to the computed address before (or simultaneously with) the memory access:
AArch64 pre-indexed load: X1 is updated to X1 + 16, then the doubleword at the new X1 is loaded into X0.
| LDR X0, [X1, #16]! // X1 = X1 + 16; X0 = Mem[X1] |
The exclamation mark in the assembly syntax signals the writeback. Pre-indexed mode is useful for walking through a data structure where each access advances the pointer: the load and the pointer update happen in a single instruction, reducing the dynamic instruction count.
Post-indexed addressing
In post-indexed mode, the memory access uses the original base register value, and the base register is updated afterward:
AArch64 post-indexed store: the doubleword in X0 is stored at the current X1, then X1 is incremented by 8.
| STR X0, [X1], #8 // Mem[X1] = X0; X1 = X1 + 8 |
Post-indexed mode is the natural fit for a loop that processes elements in order: the store writes to the current position, and the pointer advances to the next position in the same instruction.
Both pre-indexed and post-indexed modes require the base register to have a write port in the same cycle as the load or store data path. RISC-V avoids this complexity by keeping load and store semantics simple and letting the compiler emit a separate addi for the pointer update.
08.The Barrel-Shifter Operand
AArch64 arithmetic and logical instructions can apply a shift or extension to the second source operand before it enters the ALU. This barrel-shifter operand is a distinctive feature inherited from the original ARM architecture (ARMv1, 1985).
For example, the instruction ADD X0, X1, X2, LSL #3 computes . The available shift operations are LSL (logical shift left), LSR (logical shift right), ASR (arithmetic shift right), and ROR (rotate right). The shift amount is a 6-bit unsigned immediate (0–63 for 64-bit operations).
The barrel shifter adds no extra cycle because the shift is integrated into the ALU’s input multiplexer. The hardware cost is a combinational shifter on one of the ALU input paths, which is modest in area but occupies a non-trivial fraction of the ALU’s critical-path delay. RISC-V and x86-64 do not provide an equivalent: shifting a source operand requires a separate shift instruction.
The barrel-shifter operand is especially valuable for address arithmetic. Computing base + index * 8 is a single ADD with LSL #3 on AArch64, two instructions (shift then add) on RISC-V, and one instruction (using the SIB byte) on x86-64. For general-purpose scaling by powers of two, the barrel-shifter approach and the SIB-byte approach are different hardware answers to the same compiler need.
09.Comparison Table
Table 1. Addressing mode support across three ISAs. A check mark indicates native hardware support. A dash indicates that the mode is not available in the base integer ISA.
| Addressing Mode | RISC-V | AArch64 | x86-64 |
|---|---|---|---|
| Immediate | |||
| Register | |||
| Base + displacement | |||
| Base + index | — | ||
| Base + scaled index | — | ||
| Base + scaled index + disp. | — | — | |
| PC-relative (data) | |||
| Pre-indexed | — | — | |
| Post-indexed | — | — | |
| Barrel-shifted operand | — | — |
The table tells a clear story. RISC-V occupies the minimal end of the spectrum, with a single load/store addressing mode, base plus a 12-bit signed displacement. AArch64 occupies the middle ground, adding indexed modes and the barrel-shifter operand while keeping the encoding fixed-length. x86-64 occupies the maximal end, with the richest address computation available in a single instruction, at the cost of variable-length encoding and decoder complexity.
10.Encoding Side-Effects: Width Limits and Hardware Cost
The choice of addressing modes has consequences that ripple through the hardware. This section highlights three encoding side-effects that affect compiler writers and microarchitects.
Immediate field width and constant materialization
RISC-V’s 12-bit immediate means that any constant outside the range to requires a LUI + ADDI sequence. AArch64’s 12-bit unsigned arithmetic immediate (with optional 12-bit shift) covers the range 0 to 16,773,120 for page-aligned constants, and the bitmask immediate handles a different set of useful constants. x86-64’s 32-bit immediate covers almost any constant a compiler would generate for a single instruction.
The practical impact is that RISC-V code for constant-heavy workloads (hash functions, cryptographic primitives, lookup tables of magic numbers) tends to have a higher instruction count than the same algorithm on x86-64 or AArch64.
Address-generation unit complexity
On RISC-V, the AGU is a single adder: register + sign-extended 12-bit immediate. On AArch64, the AGU must also handle register + register (with optional shift), pre-indexed writeback, and post-indexed writeback. On x86-64, the AGU must handle register + register scale + displacement, which requires a shifter (for the scale) and two adders (or a three-input adder). The AGU complexity directly affects the pipeline’s cycle time and the area budget for the load-store unit.
Compiler register pressure
Addressing modes that fold more computation into the load or store instruction reduce the number of “setup” instructions the compiler must emit and free registers for other uses. The x86-64 SIB addressing mode, for example, lets the compiler keep a base pointer in one register and an index in another without needing a third register to hold the scaled address. On RISC-V, the compiler must compute the scaled address in a temporary register, which consumes one of the 32 GPRs (31 usable, since x0 is hardwired to zero). The practical difference is small for most code, but it becomes visible in tight inner loops with many live variables.
11.Why RISC-V Has Fewer Modes
The RISC-V ISA manual [1] explains the rationale directly: every addressing mode beyond base + offset adds a read port to the register file (for the index register), a multiplexer in the address path, and a writeback hazard path (for pre/post-indexed modes). In an area-constrained embedded core running at a few hundred megahertz, these additions are proportionally expensive. The RISC-V architects chose the smallest mode set that a C compiler can target without heroic effort, and they left richer address computation to explicit arithmetic instructions.
The tradeoff is measurable. Studies comparing dynamic instruction counts on the SPEC CPU benchmark suite show that RISC-V code is typically 10 to 25 percent larger in instruction count than AArch64 code for the same workload, with much of the difference coming from address computation overhead and constant materialization. The code-size gap is smaller than the instruction-count gap because RISC-V’s fixed 32-bit encoding (or 16-bit with the C extension) produces compact binaries.
For high-performance out-of-order cores, the extra instructions are largely hidden by the execution engine’s ability to issue multiple instructions per cycle. For in-order cores with narrow pipelines, the extra instructions translate directly to extra cycles. The RISC-V vector extension (Chapter 21) and the proposed “B” bit-manipulation extension partially close the gap by folding common address patterns into vector-load strides and bit-field operations.
12.Worked Examples
13.Exercises
References
- [1]Waterman, Andrew and Asanovi\'c (2024). “The RISC-V.”