“ARM has 16 registers” is a useful sentence only after adding “in AArch32.” It becomes actively confusing on a modern 64-bit phone or server, where the register names, widths, calling convention, and treatment of the program counter differ. I find it clearer to learn the two execution states side by side, then layer the ABI on top.
AArch32 at a glance
R0–R12are visible integer registers in ordinary AArch32 code.R13is conventionally and architecturally usable as the stack pointer,SP.R14is the link register,LR, commonly holding a subroutine return address.R15is the program counter,PC, with instruction-dependent read semantics.CPSRholds condition flags, execution state, masks, and control state; application code commonly refers to its application-visible flags as APSR.Some privileged modes bank selected registers in classic A/R profiles, so “16 physical registers total” is also inaccurate.
AArch64 is not a wider spelling of R0–R15
X0–X30are 31 general-purpose 64-bit registers.W0–W30name the low 32 bits of those same registers.Writing a
Wregister zeroes the upper 32 bits of its correspondingXregister.SPis separate from the numbered general-purpose register bank.X30is conventionally the link register, but it remains a general-purpose register.PCis not a general-purpose register that ordinary A64 instructions name likeX31.PSTATEcontains processor state; condition flags are accessed throughNZCV.
Register widths change instruction behavior
mov x0, #-1 // X0 = 0xffffffffffffffff
mov w0, #7 // X0 becomes 0x0000000000000007
add w1, w0, #5 // 32-bit result; write zero-extends into X1
add x2, x0, #5 // 64-bit resultThe register spelling selects the operation size
WnandXnare views of one architectural register, not independent storage.A 32-bit general-register write clears the upper half; it does not preserve stale bits.
Immediate syntax is assembler notation and may expand into one or more actual instructions.
Signedness usually comes from the instruction and later interpretation, not from a permanent register type.
What a function call does to LR and PC
bl calculate // X30 receives the return address
// execution resumes here after calculate returns
calculate:
add x0, x0, x1
ret // normally branches to X30A link register does not eliminate the stack
BLchanges control flow and writes the return address toX30.RETdefaults to the address inX30, though A64 permits another register operand.A leaf function may return without saving
X30.A non-leaf function normally preserves its incoming return address before another
BLoverwritesX30.Security extensions such as pointer authentication can alter real prologues and epilogues.
AAPCS64 assigns roles across function boundaries
X0–X7carry the first general-register arguments and return values useX0and, where required,X1.X8is an indirect-result location register in the base procedure-call standard.X9–X15are temporary caller-saved registers.X16andX17are intra-procedure-call scratch registers that veneers and PLT code may use.X18is platform-specific and portable code should avoid assuming it is free.X19–X28are callee-saved.X29is conventionally the frame pointer andX30the link register.The stack pointer must remain 16-byte aligned at public interfaces and whenever memory is accessed through SP.
Caller-saved and callee-saved are obligations
Caller-saved means a called function may overwrite the register, so the caller saves any live value it needs afterward. Callee-saved means a function that changes the register must restore its incoming value before returning. Neither phrase says that hardware performs the save automatically.
AArch32 calling conventions use different assignments
In the base AAPCS32,
R0–R3carry arguments, results, and scratch values.R4–R8,R10, andR11are normally callee-saved; roles forR9and frame-pointer use can vary by platform.R12is an intra-procedure-call scratch register.SP,LR, andPCare aliases forR13,R14, andR15.Floating-point argument rules depend on the selected ABI variant, so hard-float and soft-float objects must not be mixed casually.
Flags live in dedicated status state
cmp x0, x1 // alias of SUBS; updates N, Z, C, V
csel x2, x0, x1, hiCondition flags describe the latest flag-setting operation
Nrecords a negative-sign result,Za zero result,Ccarry/no-borrow semantics, andVsigned overflow.CMPdiscards the arithmetic result and keeps the flags.HIis an unsigned comparison condition; signed comparisons use conditions such asGTorLT.Not every arithmetic instruction updates flags—A64 distinguishes forms such as
ADDandADDS.
Floating-point, SIMD, SVE, and SME add more register state
AArch64 has 32 128-bit SIMD/floating-point registers viewed as
V0–V31, with scalarB,H,S,D, and vectorQviews.AAPCS64 uses
V0–V7for many floating-point/vector arguments and results; preservation rules apply to portions ofV8–V15.SVE adds scalable vector
Zregisters, predicatePregisters, andFFRon implementations that support it.SME adds streaming and matrix state such as
ZAand, in newer extensions,ZT0.Always match assembly and context-switch code to the implemented architectural extensions and current ABI.
System registers and exception levels are another namespace
Registers such as SCTLR_EL1, TTBR0_EL1, ESR_EL1, and ELR_EL1 configure or report privileged architectural state. Their _ELn suffix names an exception level. User-space code at EL0 cannot freely read or write privileged state; an illegal access traps rather than behaving like an ordinary X register.
Observe registers with GDB
gcc -g -O0 demo.c -o demo
gdb ./demo
(gdb) break main
(gdb) run
(gdb) info registers
(gdb) disassemble /m main...
x0 ...
x29 ...
x30 ...
sp ...
pc ...
cpsr ...Debugger names reflect the target architecture
-gpreserves debug information and-O0reduces—but does not eliminate—source/register surprises.info registersshows the stopped thread’s current architectural state.GDB may display AArch64 status as
cpsrfor debugger compatibility even though AArch64 architecture describes PSTATE fields.At higher optimization levels, variables can move, share registers, or be optimized away.
Cross-debugging requires a GDB build and remote target description that agree on architecture and extensions.
Common misconceptions to retire
Every ARM processor has 16 registers: true only as a simplified AArch32 core-register view.
LR always contains my return address: another nested call can overwrite it unless software preserves it.
PC contains the next instruction in a universal way: AArch32 PC reads are pipeline/context sensitive, while AArch64 does not expose PC as a normal GPR.
R0 or X0 has a fixed purpose: ABI roles apply at interfaces; within a function these registers hold temporary values.
A register is signed or unsigned: bits have no permanent signedness; instructions and types provide interpretation.
The compiler always maintains a frame pointer: ABI/platform policy and compiler options permit omission in many builds.
Primary Arm references
Arm A64 ISA guide: general-purpose registers explains X/W views, the general register bank, and vector state.
AAPCS64 defines current AArch64 procedure-call register roles and preservation rules.
Arm ABI repository publishes the current AAPCS32, AAPCS64, ELF, and DWARF specifications.
Arm A64 and compiler overview contrasts A64 calls and registers with earlier instruction sets.
Comments and corrections