A C compiler typically defines a calling convention - a set of rules used for mapping C function arguments and return values to the CPU's registers and stack, allowing the caller (the function making the call) and the callee (the function being called) to put and retrieve data from the same locations.
If you're writing both the callee and caller in assembly, you are free to define your own calling convention. However, if you're interfacing with C code, you will need to follow the C compiler's expectations for where this data is to be placed.
Wonderful currently uses the 20180813
version of the regparmcall
calling convention as defined by the gcc-ia16 compiler.
For typical functions, the first three arguments or words, whichever comes first, are passed via the registers
AX
, DX
and CX
, in this order. Bytes are passed via AL
, DL
and CL
. The remaining arguments are pushed
onto the stack.
Individual arguments are not split between registers and stack. A far pointer, or 32-bit integer, will be passed via DX:AX
, CX:DX
, or entirely on the stack.
For functions which contain variable arguments, all arguments are pushed onto the stack. It is the callee's responsibility to remove arguments off the stack.
For example, the following function signature:
void outportw(uint8_t port, uint16_t value);
results in the argument port
being passed in the register AL
, and value
- in the register DX
.
The following function signature:
void __far* memcpy(void __far* s1, const void __far* s2, size_t n);
results in the following calling convention:
* DX:AX
= s1
,
* stack (4 bytes allocated) = s2
,
* stack (2 bytes allocated) = n
,
* the return value is placed in DX:AX
.
The registers AX
, BX
, CX
, DX
can be modified by the callee freely. This means that the caller cannot expect their value to stay the same before and after calling the function.
Conversely, the remaining registers - SI
, DI
, BP
, DS
, ES
and SS
must be saved by the callee. This allows the caller to expect their value to stay unchanged at the expense of the callee, who
is now the party needing to ensure that this is the case - that is, that their values are the same at the beginning and end of the function.