The ARM calling convention refers to how function arguments are passed and return values are handled when making function calls in ARM assembly language. Understanding the ARM calling convention is important for writing efficient and compliant assembly code that can properly interface with other functions.
In the ARM calling convention, the first 4 integer or pointer arguments are passed in registers r0-r3. Any additional arguments are passed on the stack. The stack grows downwards towards lower addresses. Before a function is called, the caller will push the arguments onto the stack in reverse order so that the last argument is highest on the stack. The called function will pop the arguments off the stack as needed.
Floating point arguments are passed in registers s0-s15 and combination registers d0-d7. The lower numbered registers are used first, matching the order of integer argument registers. Any remaining floating point arguments are passed on the stack above the integer arguments.
For return values, ARM follows these conventions:
- Integers and pointers are returned in r0
- 64-bit integers can use r0-r1
- Floats are returned in s0
- Doubles are returned in d0
- Structs up to 4 words are returned in r0-r3
- Larger structs are returned via a hidden first parameter pointing to return buffer
The caller must reserve space for and pass a pointer to the return buffer as the first argument to functions that return large structs. By convention, this pointer is passed in r0. The callee will write the return value into the provided buffer and the caller can access it after the function returns.
When calling a function, the caller must preserve the values in r4-r11 and the stack pointer. The callee may freely modify r0-r3, r12, lr (link register for return address) and the stack pointer as needed. All other registers must be preserved by the callee across the function call.
To make a compliant function call in ARM assembly, the general steps are:
- Push callee-saved registers r4-r11 onto the stack to preserve their values
- Load function arguments into r0-r3 registers and/or push remaining args onto stack
- Branch and link to function address to call it, storing return address in lr
- Function prologue: callee pushes stack frame, saves lr if needed
- Function body: callee does work, puts return value in appropriate register(s)
- Function epilogue: callee restores stack, preserved registers, stores return address in pc
- Caller pops stack to restore preserved registers
- Caller accesses return value from correct register(s)
There are some variations and additional details regarding the ARM calling convention, such as:
- The size of stack frames and register preserving may vary slightly between ARM architecture versions
- Leaf functions that don’t call other functions may omit stack frame and register preserving
- Register use may differ when compiling for Thumb or Thumb-2 instruction sets
- C compilers may deviate slightly from standard conventions for optimization reasons
- Additional rules for handling variadic functions, stack alignment, etc.
But in general, adhering to the core conventions outlined above will ensure assembly functions are interoperable and conform to expectations. Failing to follow the conventions properly can lead to crashes, corrupted data or other bugs.
Some key points to remember are:
- Arguments go in r0-r3 registers and stack
- Caller preserves r4-r11, callee preserves all other registers
- Return integer and pointer values in r0, floats in s0, doubles in d0
- Struct returns may need return buffer passed by caller in r0
- Stack grows downwards towards lower addresses
- Follow prologue and epilogue conventions for stack frames
Consult the reference documentation for the specific ARM architecture version to get full details. But following the general ARM calling convention principles will help avoid many common errors and interoperability issues in ARM assembly programming.
Proper use of the ARM calling convention enables modular, reusable code that can work together seamlessly. Mastering the convention is a key skill for ARM developers writing low-level library functions, OS kernels, device drivers, bootloaders, and other system-level software.
With practice and familiarity, the calling convention details will become second nature. Just remember to check the reference guides for any new situations or architectures. Getting the function calls right lays the foundation for robust and efficient ARM assembly code.