Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Apple 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Therefore, 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.Support on Ko-Fi

A practical decision procedure at every call

  1. List live values. Mark every value needed after the call, including pointers, loop counters, object addresses, vector lanes, and the eventual return address.
  2. 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.
  3. 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.
  4. Protect the link register when needed. Any function that will issue a nested call must preserve its own x30 before that call.
  5. 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.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

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.

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.