Registers

Registers are the CPU's hands. Every value you compute passes through them, and every calling convention is really just an agreement about which register holds what. Learn this table once and most assembly becomes readable.

The Register File

x86-64 gives you sixteen general-purpose registers, each 64 bits wide. They are called "general-purpose" because the CPU does not care what you keep in them — the roles below are conventions, not rules enforced by hardware.

64-bit32-bit16-bit8-bitTraditional role
raxeaxaxal/ahAccumulator; return value
rbxebxbxbl/bhBase; callee-saved general use
rcxecxcxcl/chCounter (loop, shift counts)
rdxedxdxdl/dhData; high half of multiply/divide
rsiesisisilSource index (string ops)
rdiedididilDestination index (string ops)
rbpebpbpbplBase/frame pointer
rspespspsplStack pointer
r8 … r15r8d …r8w …r8b …Added by x86-64; no legacy names

Sub-Registers: The Same 64 Bits

The names are not separate storage — they are windows onto the same register. eax is the low 32 bits of rax, ax the low 16, al the low 8. Writing to a narrow window leaves the bits above it unchanged, except for the 32-bit names, which clear the upper 32 bits to zero.

    mov  rax, 0x1122334455667788   ; rax = 11223344 55667788
    mov  al,  0xFF                 ; low byte replaced: rax = 11223344 556677FF

    mov  eax, 0x00000000           ; writing a 32-bit name ZEROES the top half:
                                   ; rax = 00000000 00000000

    ; This rule is why compilers frequently use the 32-bit form on purpose:
    ; `mov eax, 5` is a shorter encoding than `mov rax, 5`.
Diagram of the x86-64 sub-register layout: rax contains eax, which contains ax, which contains al and ah

Figure 1 — one register, five names, and the zeroing rule that catches everyone once.

Why the Odd Legacy Names

ax meant "accumulator", bx "base", cx "counter", dx "data" — names from the 16-bit era when each register had one job and instructions encoded that register preferentially. The r8–r15 registers were added in 2003 with the 64-bit extension and have no such history.

Practical advice: do not name registers by their old role. Follow the calling convention instead, and use r8–r11 as scratch space when you need somewhere temporary to work.

Special-Purpose Registers

Four registers are not general-purpose at all — the CPU uses them for its own bookkeeping, and misusing them corrupts the program's control flow.

rip — The Instruction Pointer

rip holds the address of the next instruction. You never assign to it directly; instead you change it with a jump, a call, or a return. It is what makes branching, loops, and function calls possible.

rsp — The Stack Pointer

rsp points at the current top of the stack. On x86 the stack grows downward: push subtracts 8 from rsp and writes the value there; pop reads it and adds 8 back. Because call and ret use the same mechanism to store return addresses, rsp must be perfectly balanced — every push needs a matching pop.

    push    rax         ; rsp -= 8;  [rsp] = rax
    push    rbx         ; rsp -= 8;  [rsp] = rbx
    pop     rbx         ; rbx = [rsp];  rsp += 8
    pop     rax         ; rax = [rsp];  rsp += 8   (LIFO order!)

rbp — The Frame Pointer

rbp is a general-purpose register with a convention: functions copy rsp into rbp on entry so that local variables keep fixed offsets even as rsp moves. Compilers may skip this optimization at higher levels, but hand-written assembly usually keeps the frame pointer for readability.

The Flags Register

rflags is not a number you compute with — it is a collection of single bits describing the result of the last arithmetic or logical operation. It is the bridge between arithmetic and control flow.

FlagNameSet when…
ZFZerothe result was exactly zero
SFSignthe result's top bit is 1 (negative, if signed)
CFCarryan unsigned result carried out of the top bit
OFOverflowa signed result has the wrong sign
PFParitythe low byte has an even number of 1 bits

Conditional jumps do nothing but read these bits. sub rax, rbx followed by je means "jump if the subtraction produced zero", which is how an equality test is spelled in machine code. Because almost every arithmetic instruction sets the flags as a side effect, competent assembly avoids redundant cmp instructions.

Registers and the Calling Convention

When your code calls a function written by someone else — or when someone else's code calls yours — you both must agree on where arguments go, where the result comes back, and which registers must survive the call. That agreement is the calling convention; on Linux x86-64 it is the System V AMD64 ABI.

RoleRegistersMust survive a call?
First six integer/pointer argumentsrdi, rsi, rdx, rcx, r8, r9No — caller-saved
Return valuerax (and rdx for a 128-bit result)No — caller-saved
Scratch registersrax, rcx, rdx, rsi, rdi, r8–r11No — caller-saved
Preserved across callsrbx, rbp, r12–r15, rspYes — callee-saved
Stack pointerrspYes — must be restored exactly

The distinction is a contract, and breaking it produces bugs that appear far from the mistake:

  • Caller-saved (volatile): the callee may destroy these freely, so you must save them before a call if you still need the values.
  • Callee-saved (non-volatile): if your function wants to use rbx or r12–r15, you must push them on entry and pop them before returning.
my_func:
    push    rbx                 ; we intend to use rbx, so preserve it
    push    r12                 ; ...and r12

    mov     rbx, rdi            ; now safe to clobber rbx and r12
    mov     r12, rsi

    ; ... do work, result in rax ...

    pop     r12                 ; restore in reverse order (LIFO)
    pop     rbx
    ret

A Look at ARM64 Registers

If you learn one register file, the others become easy to read. ARM64 names its thirty-one general-purpose registers x0–x30, and the same convention idea applies:

RoleARM64 registerx86-64 analogue
Arguments 0–7 and return valuex0–x7rdi, rsi, … rax for the result
Link register (return address)x30 / lrstored on the stack by call
Stack pointersprsp
Zero registerxzrnone — write a throwaway register
Status flagsnzcvrflags

The biggest structural difference: ARM has a dedicated link register holding the return address, while x86 pushes it onto the stack automatically. That single design choice explains a lot of ARM assembly's shape.

Summary

  • x86-64 has sixteen 64-bit general-purpose registers; each one is addressable at 64, 32, 16, and 8 bits.
  • Writing to a 32-bit name like eax zeroes the upper half of rax — a detail that matters when your pointers look wrong.
  • rsp, rbp, rip, and rflags are special: never repurpose them casually.
  • The System V ABI fixes argument registers, the return register, and which registers must be preserved across calls.
  • ARM64 uses x0–x7 for arguments, xzr for a zero register, and lr for the return address.

Next: Data Definitions — how values leave the registers and take up residence in memory.