The ARM calling convention refers to the standard procedure used on ARM architectures for function calls between software modules. It defines the way arguments are passed to functions and the way return values are passed back. Understanding the ARM calling convention is important for writing compliant and interoperable code for ARM processors.
Function Argument Passing
On ARM, the first 4 integer or pointer arguments are passed in registers R0-R3. Any remaining arguments are passed on the stack. Floating point and SIMD arguments are passed in S0-S15 and D0-D7 registers. The caller function is responsible for pushing arguments to the stack before the call instruction and popping them after the call returns.
For varargs functions, R0-R3 are used for the first 4 arguments as normal. Additional arguments are pushed onto the stack. The total number of arguments is passed to the callee in R12. Floating point varargs are passed in S0-S15 and D0-D7 as usual.
Integer return values up to 64 bits are returned in R0. Larger integer values are returned in R0-R1. Floating point return values are returned in S0 for floats, D0 for doubles, and S0-S1 for long doubles.
For structure returns, the caller passes a pointer to the return structure in R0 and the callee populates the return value directly into the passed structure. This avoids copying the return structure.
Caller and Callee Responsibilities
The caller function is responsible for:
- Pushing arguments to the stack if more than 4
- Allocating stack space for any spill registers it needs to use
- Making the call preserving link register LR
- Cleaning up the stack after the call
The callee function is responsible for:
- Preserving R4-R11 if needed
- Allocating stack space for any spill registers it needs
- Restoring R4-R11 before returning
- Returning values in the correct registers
- Ensuring the stack is aligned to 8 bytes on public interfaces
The ARM EABI requires the stack to be 8-byte aligned on any public interface. This ensures efficient memory access. The caller is responsible for aligning the stack before a call. The callee must maintain 8 byte alignment on its own stack frame. Stack pointers should be restored before returning from a function call.
Parameter Passing Methods
Arguments can be passed to functions in ARM using different methods:
- Registers – Up to 4 arguments passed in R0-R3.
- Stack – Remaining arguments passed on the stack.
- Registers + Stack – First arguments in R0-R3, remaining on stack.
Different data types like integers, floats, pointers, structures, etc. can be passed using these methods. The ARM calling convention standardized which registers and stack positions are used for each argument.
Handling Large Data
For large data structures that don’t fit in registers, a pointer is passed instead. The callee can then access the data directly through the pointer. For returning large values, a pointer to a return structure is passed in R0.
Varargs functions have a variable number of arguments. The total number of arguments is passed in R12. Varargs are still passed in registers R0-R3 first if there are 4 or fewer. Extra arguments are pushed onto the stack. Floating point varargs are passed in S and D registers.
Following the standard ARM calling convention enables object code from different compilers and languages to interoperate. Code compliant to the ARM EABI can easily be linked together. This is critical for reusable libraries and combining code from different modules.
Caller Saved vs Callee Saved Registers
Registers are designated as either caller-saved or callee-saved:
- Caller-saved – R0-R3, R12. The caller must preserve these if needed.
- Callee-saved – R4-R11, LR. The callee must preserve these if modified.
This convention determines which function is responsible for saving and restoring a register’s value during a call.
To preserve a register’s value across a function call, it can be pushed onto the stack in the function prologue and popped back in the epilogue. Alternatively, a callee can save registers in its own stack frame if needed.
The ARM EABI requires 8-byte stack alignment at public interfaces. This enables efficient access to 8-byte data like doubles. Stack pointers must be realigned after pushing arguments.
The standard calling convention only applies to public interfaces. Private code doesn’t need to maintain stack alignment or use the standard register assignments. However, following the convention also for private code improves interoperability and compatibility.
During context switches, the kernel must save any registers modified by a process before switching to another process. This is done by pushing callee-saved registers to the process stack or storing them elsewhere.
Register Usage Optimization
Registers can be used more flexibly within a function body than at the interface:
- Arguments in R0-R3 can be overwritten right after use
- R4-R11 scratch registers are available for local variables
- Unneeded FP arguments can be reused earlier
This optimization reduces unnecessary save/restore of registers internally.
Older ARM dialects had different conventions regarding register assignments and stack alignment. Code needs to be recompiled sometimes to conform to the newer EABI standard for compatibility.
Code that doesn’t follow the standard calling convention may still work correctly in some cases. But it can lead to crashes or undefined behavior when interfacing with other code. Strict compliance is needed for compatibility.
With nested function calls, each caller must preserve LR to return to the right address. Callee-saved registers should only be modified in the outermost call. Stack parameters and spill slots must be readjusted on each level.
When debugging code, understanding the calling convention is critical for setting breakpoints, examining stack frames, and tracing function arguments. The convention determines the inner workings of a function call in assembly.
Function pointers must point to functions with the standard calling convention. Otherwise, a called function will crash or behave incorrectly when passed arguments in the wrong registers or stack positions.
A leaf function only calls other leaf functions, not public ones. This means callee-saved registers don’t need preservation. Parameters and return values still follow the standard convention.
Reentrant functions can have multiple activations on the stack during recursion or reentrant calls. The stack pointers must be adjusted separately for each activation to access local variables.
Dynamically linked code must adhere to the standard calling convention for compatibility. Position-independent code relies on standard register roles and stack usage to work correctly.
Embedded ARM processors often have custom calling conventions optimized for the target system. However, compliance with the EABI convention enables integration with standard compilers and libraries.
Operating System Interfaces
The OS kernel interface follows its own custom convention optimized for low-level operations. But OS services called from userspace applications typically follow the standard EABI convention.
Efficient register usage and optimized stack management provides maximum performance for ARM code. But improper handling of registers and stack according to convention can significantly degrade performance.
In summary, adhering to the standard ARM calling convention ensures efficient, portable, and interoperable code for ARM platforms. Though some low-level or system code may deviate, compliance allows seamless interfacing with high-level languages, compilers, libraries, and applications built on ARM.