Calling Conventions
Background#
When writing code in assembly, we want to be able to share our code with others and use others’ code. To do this we may compartmentalize useful features and functionality into functions. However, because we’re working in assembly, things like scoping, etc. don’t really exist. This means that it is very easy to step on each other’s toes, eg. overwriting the value in a register that was important to the other person.
Considering these issue, we are left with the following questions to be solved:
- how do we pass information (i.e., parameters) to a function?
- how do we get information (i.e., return values) back?
- which registers to use / modify and which to leave alone?
And indeed, these are exactly the things that a calling convention solves. To sum it up as a definition:
calling convention#
a contract between the caller (the code which makes the function call with
bl <label>
) and the callee (the code between <label>
and the bx lr
instruction).
There are many different calling conventions, and it is usually okay to use whichever, as long as the caller and callee agree on the same one (although hardware support also has a role to play).
AAPCS#
The ARMv7 Architecture Procedure Call Standard is the convention we’ll (try to) adhere to in programming our microbits.
The full standard is quite detailed, but the general summary is:
r0
-r3
are the parameter and scratch registers (also referred to as “caller-save”)r0
-r1
are also the result registersr4
-r11
are callee-save registersr12
-r15
are special registers (ip
,sp
,lr
,pc
)
What are scratch registers?#
r0
-r3
are “scratch” registers, which means that the callee can freely use
them (and not worry about messing anything up for the caller).
These are also called “caller-save” registers, because if the caller wants to preserve the values in them they need to save them somewhere.
What are callee-save registers?#
r4
-r11
are “callee-save” registers, this means that when you make a function call,
you can assume that values in r4
to r11
will be preserved (the same) when the function
call returns. This does not mean that the function won’t use those registers, it just
means that, if they are used, they will be restored to the same state as they were when
the function was first called.
How does the stack fit into all this?#
Generally speaking, the stack pointer can be assumed to be in the same place both when the function was called and when the function returns. This changes when arguments are either passed via the stack, or results are returned via the stack. In these cases, it is assumed that the interface for the function is public, and that the stack pointer should, in both cases, be pointing to the top of the stack of arguments / results, with the order and size of the arguments / results being defined in the function interface.
eg:
@@@ do_all_the_things
@ description of function goes here
@@ Arguments:
@ r0: a
@ r1: b
@ r2: c
@ r3: d
@ stack: e, f, g (32 bits each)
@@ Return
@ r0: w
@ r1: x
@ stack: y, z (32 bits each)
do_all_the_things:
@ ...
@ bx lr
and here is what the stack frame for this example would look like