Home | Projects | Notes > ARM Cortex-M3/M4 Processor > ARM Architecture Procedure Call Standard (AAPCS)
The AAPCS standard describes procedure call standard used by the application binary interface (ABI) for ARM architecture.
The AAPCS defines how subroutines can be separately written, separately compiled, and separately assembled to work together. It describes a contract between a calling routine and a called routine that defines:
Obligations on the caller to create a program state in which the called routine may start to execute.
Obligations on the called routine to preserve the program state of the caller across the call
The rights of the called routine to alter the program state of its caller
When a C compiler compiles code for the ARM architecture, the generated assembly code must be AAPCS compliant.
If you are writing a pure assembly program, make sure that it follows AAPCS.
If your are writing a C program, you need not worry about AAPCS because it is your C compiler's responsibility to generate an AAPCS compliant assembly program.
R0, R1, R2, R3, R12, R14(LR) registers are called "caller-saved registers". It's the responsibility of the caller to save these registers on stack before calling the function if those values will still be needed after the function call and retrieve it back once the called function returns. Register values that are not required after the function call don't have to be saved.
xxxxxxxxxx111Caller:2 @ use r0, r1, r3, r12, r143 @ push {r0-r3, r12, r14} : caller's responsibility4 bl Callee5 @ pop {r0-r3, r12, r14} : caller's responsibility*/6 @ use r0, r1, r3, r12, r14 */7...8Callee:9 @ use r0-r3, r12, r14 10 mov pc, lr @ return to the caller11}R4 to R11 are called "callee-saved registers". The function or subroutine being called needs to make sure that, contents of these registers will be unaltered before exiting the function.
xxxxxxxxxx111Caller:2 @ use r4-r113 bl Callee4 @ use r4-r115...6Callee:7 @ push {r4-r11} : callee's responsibility 8 @ use r4-R11 9 @ pop {r4-r11} : callee's responsibility*/10 mov pc, lr @ return to the caller11}According to this standard, caller function uses R0, R1, R2, R3 registers to send input arguments to the callee function.
The callee function uses registers R0 and R1 (only if the result size is 64-bit) to send the result back to the caller function.

If there are more than 4 arguments to be passed,
First 4 will be passed via registers
The rest will be passed via stack (using r4-r8, r10, r11 registers that are named "Variable-register
For more information, see Procedure Call Standard for the Arm Architecture,

See how the compiler generated assembly code is AAPCS compliant.
To view the assembly code generated from your project: <Project>/Debug/<Project>.list
xxxxxxxxxx351/* main */2
3int main(void)4{5 80002e0: b580 push {r7, lr}6 80002e2: b082 sub sp, #87 80002e4: af00 add r7, sp, #08 change_sp_to_psp();9 80002e6: f7ff ffe7 bl 80002b8 <change_sp_to_psp>10
11 int ret;12 ret = fun_add(1, 4, 5, 6);13 80002ea: 2306 movs r3, #614 80002ec: 2205 movs r2, #515 80002ee: 2104 movs r1, #416 80002f0: 2001 movs r0, #117 80002f2: f7ff ffcd bl 8000290 <fun_add>18 80002f6: 6078 str r0, [r7, #4]19
20 printf("result = %d\n", ret);21 80002f8: 6879 ldr r1, [r7, #4]22 80002fa: 4803 ldr r0, [pc, #12] ; (8000308 <main+0x28>)23 80002fc: f000 f932 bl 8000564 <iprintf>24
25 generate_exception();26 8000300: f7ff ffe6 bl 80002d0 <generate_exception>27
28 /* Loop forever */29 for(;;);30 8000304: e7fe b.n 8000304 <main+0x24>31 8000306: bf00 nop32 8000308: 08001574 .word 0x0800157433
340800030c <SVC_Handler>:35}L13-L16: Notice that r0-r3 registers are used to pass 4 arguments!
xxxxxxxxxx311/* fun_add */2
308000290 <fun_add>:4#if !defined(__SOFT_FP__) && defined(__ARM_FP)5 #warning "FPU is not initialized, but the project is compiling for an FPU. Please initialize the FPU before use."6#endif7
8int fun_add(int a, int b, int c, int d)9{10 8000290: b480 push {r7}11 8000292: b085 sub sp, #2012 8000294: af00 add r7, sp, #013 8000296: 60f8 str r0, [r7, #12]14 8000298: 60b9 str r1, [r7, #8]15 800029a: 607a str r2, [r7, #4]16 800029c: 603b str r3, [r7, #0]17 return a+b+c+d;18 800029e: 68fa ldr r2, [r7, #12]19 80002a0: 68bb ldr r3, [r7, #8]20 80002a2: 441a add r2, r321 80002a4: 687b ldr r3, [r7, #4]22 80002a6: 441a add r2, r323 80002a8: 683b ldr r3, [r7, #0]24 80002aa: 4413 add r3, r225}26 80002ac: 4618 mov r0, r327 80002ae: 3714 adds r7, #2028 80002b0: 46bd mov sp, r729 80002b2: bc80 pop {r7}30 80002b4: 4770 bx lr31 ...L10: Callee is pushing the current status of r7 (callee-saved register) onto the stack so it can use r7 in its body.
L11-L14: Notice that the local variables a, b, c, d retrieve their values from the registers r0-r3.
L26: The result stored in r3 is moved to r0 as per AAPCS.
To allow a C function to be used as an exception/interrupt handler, the exception mechanism needs to save r0-r3, r12, R14(LR), and XPSR automatically at the entrance of exception and restore them at the termination of the exception under the control of the processor hardware.
In this case, the processor will save the caller-saved registers (since there is no caller for an exception/interrupt handler) and restore them when the exception/interrupt handler returns.
Simply put, the hardware generates exceptions/interrupts, and the hardware needs to take care of it!
You, as a programmer, do not need to worry about AAPCS rules when writing exception/interrupt handlers as regular C functions. Even the compiler won't have to worry about it because the processor will take care of this situation.

Nayak, K. (2022). Embedded Systems Programming on ARM Cortex-M3/M4 Processor [Video file]. Retrieved from https://www.udemy.com/course/embedded-system-programming-on-arm-cortex-m3m4/