Functions, Stack and the ABI¶
A nested call changes the link register, and a callee may overwrite temporary registers. Preserve exactly the state your function needs, then restore the caller's stack.
Course examples on GitHub
Before you start¶
Complete addresses and relocations. You understand leaf returns, register widths, pointers and loops.
Outcome: write a non-leaf routine that keeps a live argument across a call and returns with its frame balanced. Work in a fresh directory on the Pi.
The AAPCS64 function boundary¶
For the ordinary integer/pointer calls in this course:
| Registers | Boundary obligation |
|---|---|
| x0–x7 | Arguments; scalar integer/pointer result in x0 |
| x8 | Indirect-result location, not an extra ordinary argument |
| x9–x17 | Caller-clobbered temporaries |
| x18 | Platform role; avoid portable scratch use |
| x19–x29 | Callee-preserved |
| x30 / LR | Return address overwritten by BL |
| SP | Restore it; keep 16-byte alignment |
These are calling rules, not permanent hardware types. A live temporary needed after a call must be saved by its caller. Follow the AAPCS64 for aggregate, variadic and FP signatures rather than extrapolating this table. Flags are not preserved across public calls.
Why a leaf template is not enough¶
bl helper places the address after BL in LR. Without saving the incoming LR, a later RET can return into the current function instead of its caller. A leaf function that makes no calls does not automatically need a stack frame.
This example computes 2 * value + bonus modulo 2^64. It retains bonus in callee-preserved x19 while a helper deliberately overwrites x1. It also restores the caller's original x19.
Draw the frame¶
After allocation, offsets 0 and 8 hold saved FP and LR, offset 16 holds saved x19, and offset 24 is padding. Total allocation is 32 bytes, preserving 16-byte alignment. Pre-index [sp, #-32]! allocates before the store; post-index [sp], #32 restores after the load.
x29 is a frame pointer here; it is not mandatory in every leaf. This teaching frame contains no unwind metadata, so do not assume full exception-unwinding support. Production mixed-language code needs an appropriate unwind contract as well as correct runtime registers.
Inspect preservation rather than guessing¶
In GDB, stop at the exact function entry, note SP and x19, step across the body and stop immediately before RET. They must match their entry values. The GDB lesson provides a repeatable command sequence.
If return flow repeats unexpectedly, check LR saving first. If bonus becomes zero, check whether it was left in x1 across the helper call. Do not repair either bug by assuming the helper will never change caller-saved registers.
Part 2 checkpoint¶
Trace one pointer load, one relocation pair and one nested call. Explain why register width, allocated object bounds and calling-convention preservation are separate obligations. Then predict double_plus(3, 4) (10) before adding the test.
Check your understanding¶
Why save x19 before putting bonus into it? Why allocate 32 bytes when the saved values occupy 24?
Compare your reasoning
The caller may already have a live x19 value, so this callee must restore it. Padding keeps SP aligned to 16 bytes while holding all three saved registers.
Continue the course¶
Previous: Addresses and Relocations · Course roadmap · Next: Calling Assembly from C and C++