A function call sounds simple — jump there, jump back. But how does the caller pass arguments, get a result, and trust its registers survive? The answer is a shared rulebook: the AAPCS. Master it and your assembly can call C, and C can call yours.
From Day 12: BL calls (jump + save return address in LR), and you return with BX LR (or by popping LR into PC):
If everyone invented their own way to pass arguments and use registers, no two pieces of code could call each other. So ARM defines the AAPCS (ARM Architecture Procedure Call Standard) — the contract every compiler and library follows. Obey it and your code interoperates with the whole ecosystem.
| Registers | Role | Who preserves |
|---|---|---|
| r0–r3 | arguments 1–4; r0 (& r1) = return value; scratch | caller-saved |
| r4–r11 | local variables | callee-saved |
| r12 (IP) | scratch / intra-call temp | caller-saved |
| r13 (SP) | stack pointer | special |
| r14 (LR) | return address | special |
| r15 (PC) | program counter | special |
It splits the work fairly. Scratch registers (r0–r3) are for quick throwaway use — no one saves them, so calls are cheap. Long-lived values go in r4–r11, which the callee promises to protect, so the caller can rely on them surviving any call.
Here's the catch that bites beginners. BL overwrites LR. So if your function calls another function, the second BL destroys your own return address — unless you save LR on the stack first (Day 14):
A leaf function (one that calls nothing) can skip this and just BX LR. Any function that makes a call must preserve LR. This is the single most common cause of "my function returns to the wrong place."
The AAPCS is the handshake between functions: r0–r3 carry args, r0 returns the result, r4–r11 are protected by the callee, and LR holds the return address — which you must save on the stack before any nested call. Follow it and your assembly and C interoperate perfectly.
POP {…,pc} returns.The ARM Architecture Procedure Call Standard — the convention for argument/return registers, register preservation and stack use that lets ARM code interoperate.
Caller-saved (r0–r3, r12) can be clobbered by the callee; callee-saved (r4–r11) must be preserved by the called function.
BL overwrites LR, so a function that calls another must push LR first or it loses its own return address.