Join GitHub today
GitHub is home to over 28 million developers working together to host and review code, manage projects, and build software together.Sign up
Parameter passing and calling conventions
Table of Contents
Parameters are passed to functions on the parameter stack. In general, parameters are pushed from left to right, so the rightmost parameter is the last one pushed (and therefore, the one at the lowest position on the parameter stack). Provided that there are no local variables, the last parameter is at offset zero on the parameter stack. If the function returns a value, it comes back in A/X.
The compiler expects all return values to use X, even if they are declared as an 8-bit type. X should always contain the high byte of the return value as if it was promoted to a 16-bit integer.
In the presence of a prototype, parameters are pushed as their respective types. That especially does mean that characters are pushed as such (one byte), and are not promoted to integers.
If no prototype is available, the default promotions are applied before pushing parameters. That means that characters are promoted to integers before pushing them. The Y index register is set to the number of bytes (not parameters) that were pushed onto the stack.
In the presence of an old-fashioned Kernighan & Ritchie prototype, parameters are treated the same as if there were no prototype.
Parameters in variable argument lists (ellipsis, …) are treated the same as if there were no prototype. The Y register counts all of the bytes that were pushed, including the ones in the defined, fixed part of the list.
If a function is declared as __fastcall__ (or fastcall), the last (rightmost) parameter is not passed on the stack, but passed in the primary register to the called function. That is A in the case of an eight-bit value, A/X in the case of a 16-bit value, and A/X/sreg in the case of a 32-bit value.
If the called function is a C function, its first instruction will be a call to one of the 'push' functions to push the passed value onto the stack. That means that, for C functions, 'fastcall' doesn't make the code really faster. Assembler functions, however, can take advantage of values being passed in registers.
Although 'fastcall' doesn't help to make C functions faster, it usually helps to make the whole program somewhat smaller, as all the callers of a 'fastcall' function can omit one call to a 'push' function.
Contrary to most other C compilers, the callee (the called function) is responsible for cleaning up the stack (dropping stack space used for parameters) before returning. That is done in order to generate smaller code, because dropping parameters can be combined, in many places, with dropping local variables.
The CPU's A, X, Y, and processor flags registers are used to pass data back-and-forth between the caller and the callee; therefore, they don't need to be saved and restored. Some of the pseudo-registers (sreg, regsave, ptr''n'', and tmp''n''), also, don't need preservation.
The pseudo-register array regbank does need to be preserved. And, the sp parameter stack pointer needs to be "protected" (it will change if you clean the stack).
The following example assumes that there are no local variables. The presence of local variables would change the stack offset of the parameters in the function.
void cdecl foo(unsigned bar, unsigned char baz);
Stack layout within the function:
+------------------+ | High byte of bar | Offset 2 ->+------------------+ | Low byte of bar | Offset 1 ->+------------------+ | baz | Offset 0 ->+------------------+
Example code for accessing
bar. The variable is in A/X after this code snippet:
ldy #2 ; Offset of high byte of bar lda (sp),y ; High byte now in A tax ; High byte now in X dey ; Offset of low byte of bar lda (sp),y ; Low byte now in A