iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more
Under the base AAPCS64 calling convention, x19–x29 and the stack pointer (SP) must retain their incoming values across a public function call. The called function (the callee) saves and restores any of those registers it changes. Most other general-purpose registers—including x0–x17—are caller-saved, so the caller must preserve any live value before issuing BL or another call. SIMD registers follow a separate rule: v0–v7 and v16–v31 are caller-saved, while only the low 64 bits of v8–v15 are guaranteed to survive.
These are the base AAPCS64 rules. An operating-system ABI can assign an additional role to x18, impose frame-record requirements, or select an extended procedure-call standard for SVE or SME. Always identify the target platform before treating a register as portable scratch space.
The call boundary is a contract
A function call is not a promise that every register keeps its incidental value. It is a contract assigning preservation work to the caller and callee. A caller-saved register may be overwritten by the function it calls. A callee-saved register must come back with the same value it had at entry if the callee changed it.
Free tools Windows power users keep installed
One-click scans. No signup required.
That division lets separately assembled or compiled routines interoperate. A short-lived calculation can use a caller-saved register without a save-and-restore sequence in every callee. A value that must remain live through several calls can occupy a callee-saved register, with save/restore cost paid only by a function that actually uses that register.
#1 Best Overall
General-purpose register map
| Registers | Base AAPCS64 role | After a call |
|---|---|---|
| x0–x7 | Integer and pointer arguments and return values | Caller-saved; contents are not guaranteed |
| x8 | Indirect result-location register | Caller-saved |
| x9–x15 | General temporary registers | Caller-saved |
| x16 (IP0), x17 (IP1) | Intra-procedure-call temporaries; available to linker veneers and PLT code | Caller-saved; never keep an essential value live across a call |
| x18 | Platform-defined register, or caller-saved when the platform assigns no dedicated role | Consult the target platform ABI |
| x19–x28 | General callee-saved registers | Full 64-bit value preserved |
| x29 (FP) | Frame pointer and callee-saved register | Full 64-bit value preserved, subject to platform frame-record rules |
| x30 (LR) | Link register; BL writes the return address here |
Not an ordinary callee-saved register; a non-leaf function normally saves it before making another call |
| SP | Stack pointer | Preserved and kept within the ABI’s stack constraints |
“Preserved” means the incoming 64-bit value, not merely the low word, is restored for x19–x29. A callee does not have to save every register in that range in every prologue; it saves only the callee-saved registers it actually changes.
Caller-saved registers: what the caller must do
Before a call, inspect every value that will still be needed afterward. If that value is in x0–x17, or in a platform-designated caller-saved register, move it to a safe location or spill it to the stack. The called function is free to use those registers for arguments, return values, address calculations, veneers, and temporary work.
A value in x9 that must survive
// x9 contains a live pointer or integer before the call
mov x19, x9 // move it to a callee-saved register
bl worker // worker may overwrite x9
mov x9, x19 // recover the value after worker returns
This example assumes the surrounding function is allowed to use x19. Because changing x19 creates a callee obligation, that surrounding function must save x19 on entry and restore it before returning. A stack spill is another option when reserving a callee-saved register would cost more than the value is worth.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Argument and result registers are not storage
x0–x7 carry ordinary integer or pointer arguments and results, so a call commonly destroys values that were there before the call. x8 can carry the address where an indirect result is written and is likewise caller-saved. Do not expect an argument in x0 to remain available after the callee returns unless you saved it first.
Rank #2
IP0 and IP1 are especially volatile
x16 and x17 are named IP0 and IP1 (intra-procedure-call temporaries). Linker-generated veneers and procedure-linkage-table code may use them even when the source-level callee appears not to. Treat both as disposable at every call boundary.
Callee-saved registers: what the callee must do
If a function changes x19, x24, x29, or another register in the x19–x29 set, it must arrange for the original 64-bit value to be restored before returning. A typical non-leaf function therefore has a prologue that stores the registers it will modify and an epilogue that reloads them.
function:
// Prologue: save only the callee-saved state this function will change
stp x29, x30, [sp, #-16]!
mov x29, sp
str x19, [sp, #-16]!
// body may use x19 and may call another function
bl helper
// Epilogue restores the incoming values
ldr x19, [sp], #16
ldp x29, x30, [sp], #16
ret
The exact frame layout is an implementation choice, but the obligations are not: SP must be returned to its incoming value, and every changed callee-saved register must be restored. A leaf function that never changes x19–x29 may omit their saves. A function that uses x19 but not x20 need not save x20.
Recommended Free Tools
Why x30 (LR) is different
BL target places the return address in x30, the link register. If the current function then executes another BL, that nested call overwrites x30. To return to its own caller, the current function must preserve its original link value, commonly by storing x30 with the frame record or another stack slot.
Rank #3
This makes x30 distinct from the ordinary x19–x29 callee-saved group. A leaf function can often return directly with ret while x30 still holds its caller’s address. A non-leaf function normally saves and restores x30 because it needs to make at least one further call.
SP and frame-pointer rules
SP is preserved across a public call and must remain valid under the target ABI’s stack constraints throughout the function. If x29 is used as a frame pointer, it must be restored like any other callee-saved register. Some platforms add frame-record rules; Apple ARM64 documentation, for example, requires x29 to address a valid frame record. Do not infer one operating system’s frame requirements from another.
The x18 portability trap
Base AAPCS64 deliberately leaves x18 to the platform. A system ABI may reserve it for inter-procedural state, or may treat it as another caller-saved temporary when no dedicated role is needed. Arm advises platform-independent hand-written assembly to avoid x18 where possible.
Outdated Drivers Are Slowing You Down
One free scan finds every outdated or missing driver and matches the right update for your exact hardware.Free scan · exact hardware matchWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallApple platforms reserve x18. Code that freely uses x18 on a different system can therefore corrupt platform state when moved to Apple, while code written for Apple may unnecessarily avoid a register that is available elsewhere. Check the operating-system ABI and toolchain documentation before assigning x18 a role.
SIMD and floating-point registers
| Registers | Preservation under the base SIMD rule |
|---|---|
| v0–v7 | Caller-saved; no bits are guaranteed after a call |
| v8–v15 | Only the low 64 bits of each register are callee-saved |
| v16–v31 | Caller-saved; no bits are guaranteed after a call |
The middle row is easy to misread. A 64-bit value in the low half of v8–v15 survives, but a 128-bit value occupying the whole register does not automatically survive. If a larger vector remains live across a call, the caller must save it or use an interface whose rules explicitly preserve it.
Example: protecting a 128-bit value
// q8 is a 128-bit value in v8; only its low 64 bits are ABI-preserved
str q8, [sp, #-16]!
bl vector_worker
ldr q8, [sp], #16
The stack spill is illustrative; choose a layout that satisfies the target ABI’s stack constraints and preserves any other live state.
SVE and SME require the matching PCS
Scalable Vector Extension (SVE) introduces conditional preservation rules. When a routine passes or returns scalable vector or predicate arguments, AAPCS64 specifies preservation for z8–z23 and p4–p15. Without that kind of interface, the conditional rules differ from the base SIMD summary.
Do these 3 things before closing this tab:
1Scan for outdated or missing drivers - takes under a minute2Repair Windows errors before they cause bigger problems3Fix the driver behind crashes, sound loss and screen glitchesTherefore, do not apply the v-register table blindly to SVE or SME code. Identify whether the function uses a scalable-vector or matrix extension procedure-call standard and consult the corresponding current ABI section. The interface, not merely the presence of an SVE instruction, determines which extended rules apply.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.A practical decision procedure at every call
- List live values. Mark every value needed after the call, including pointers, loop counters, object addresses, vector lanes, and the eventual return address.
- Classify each register. x0–x17, IP0/IP1, and the caller-saved SIMD registers are volatile. x19–x29 are callee-saved under base AAPCS64. Treat x18 according to the platform ABI. Treat v8–v15 as preserving only their low 64 bits.
- Choose an owner for each save. The caller saves a live caller-saved value before the call. The callee saves a callee-saved register only when it changes that register.
- Protect the link register when needed. Any function that will issue a nested call must preserve its own x30 before that call.
- Restore in the reverse responsibility. Reload callee-saved registers and the link register, undo the stack adjustment, and return with the original SP and required frame state.
Reading and debugging AArch64 assembly
Spot an invalid caller assumption
If code reads x0, x9, x16, or a SIMD caller-saved register after BL without a preceding save, it is relying on an ABI violation or an accidental property of one implementation. The bug may appear only after a linker veneer, optimization change, or replacement callee uses that register.
Spot an invalid callee
Track writes to x19–x29 and SP from entry to return. A function that writes one of those registers but has no matching restore is non-conforming. Also check non-leaf functions for preservation of x30; a missing link-register save often manifests as a return to the wrong address rather than an obvious register corruption.
Check width, not just register names
For x19–x29, preservation covers all 64 bits. For v8–v15, preservation covers only the low 64 bits. A debugger showing an unchanged lower half of a vector register does not prove that a 128-bit value survived.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →Common misconceptions
- “All x0–x18 are caller-saved.” This is too broad because x18 is platform-defined. Consult the target ABI.
- “A callee always saves x19–x29.” It saves only the callee-saved registers it actually changes.
- “v8–v15 are fully preserved.” Under the base rule, only their low 64 bits are preserved.
- “x30 is just another preserved register.” x30 is the link register and is overwritten by nested calls; non-leaf functions normally save it explicitly.
- “If a function happens not to overwrite x9 today, x9 is safe.” Caller-saved means no such guarantee. A veneer, PLT sequence, compiler change, or future edit may overwrite it.
Portable assembly checklist
- State the target operating system and ABI, especially for x18 and frame records.
- Keep values that must cross a call in a callee-saved register or in an explicit spill slot.
- Save every callee-saved register you modify, and restore its full incoming value.
- Save x30 before making a nested call if the function must later return to its own caller.
- Assume x0–x17 and caller-saved SIMD registers are disposable after a call.
- Preserve the full vector when a 128-bit or larger value in v8–v15 remains live; the base guarantee covers only 64 bits.
- Maintain SP and any required frame-record and stack constraints at every public interface.
- For SVE or SME interfaces, use the applicable extended PCS instead of the base register summary.
What to remember
For ordinary base AAPCS64 code, the shortest reliable rule is: the caller protects volatile state; the callee restores nonvolatile state. x19–x29 and SP are the general-purpose state that survives a public call. x0–x17 are volatile, with x16 and x17 available to linkers and x18 subject to the platform. x30 must be saved by a function that would otherwise lose its return address to a nested call. In SIMD, only the low halves of v8–v15 are nonvolatile. Apply those rules at the actual call boundary, then refine them for the operating system and any SVE/SME procedure-call standard in use.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

