The compiler always uses the global registers DPL, DPH, B and ACC to pass the first (non-bit) parameter to a function, and also to pass the return value of function; according to the following scheme: one byte return value in DPL, two byte value in DPL (LSB) and DPH (MSB). three byte values (generic pointers) in DPH, DPL and B, and four byte values in DPH, DPL, B and ACC. Generic pointers contain type of accessed memory in B: 0x00 – xdata/far, 0x40 – idata/near – , 0x60 – pdata, 0x80 – code.
The second parameter onwards is either allocated on the stack (for reentrant routines or if —stack-auto is used) or in data/xdata memory (depending on the memory model).
Bit parameters are passed in a virtual register called 'bits' in bit-addressable space for reentrant functions or allocated directly in bit memory otherwise.
Functions (with two or more parameters or bit parameters) that are called through function pointers must therefor be reentrant so the compiler knows how to pass the parameters.
Unless the called function is declared as _naked, or the —callee-saves/—all-callee-saves command line option or the corresponding callee_saves pragma are used, the caller will save the registers (R0-R7) around the call, so the called function can destroy they content freely.
If the called function is not declared as _naked, the caller will swap register banks around the call, if caller and callee use different register banks (having them defined by the __using modifier).
The called function can also use DPL, DPH, B and ACC observing that they are used for parameter/return value passing.
In the following example the function c_func calls an assembler routine asm_func, which takes two parameters.
extern int asm_func(unsigned char, unsigned char);The corresponding assembler function is:
int c_func (unsigned char i, unsigned char j)
{
return asm_func(i,j);
}
int main()
{
return c_func(10,9);
}
.globl _asm_func_PARM_2The parameter naming convention is _<function_name>_PARM_<n>, where n is the parameter number starting from 1, and counting from the left. The first parameter is passed in DPH, DPL, B and ACC according to the description above. The variable name for the second parameter will be _<function_name>_PARM_2.
.globl _asm_func
.area OSEG
_asm_func_PARM_2:
.ds 1
.area CSEG
_asm_func:
mov a,dpl
add a,_asm_func_PARM_2
mov dpl,a
mov dph,#0x00
ret
In this case the second parameter onwards will be passed on the stack, the parameters are pushed from right to left i.e. before the call the second leftmost parameter will be on the top of the stack (the leftmost parameter is passed in registers). Here is an example:
extern int asm_func(unsigned char, unsigned char, unsigned char) reentrant;The corresponding (unoptimized) assembler routine is:
int c_func (unsigned char i, unsigned char j, unsigned char k) reentrant
{
return asm_func(i,j,k);
}
int main()
{
return c_func(10,9,8);
}
.globl _asm_funcThe compiling and linking procedure remains the same, however note the extra entry & exit linkage required for the assembler code, _bp is the stack frame pointer and is used to compute the offset into the stack for parameters and local variables.
_asm_func:
push _bp
mov _bp,sp ;stack contains: _bp, return address, second parameter, third parameter
mov r2,dpl
mov a,_bp
add a,#0xfd ;calculate pointer to the second parameter
mov r0,a
mov a,_bp
add a,#0xfc ;calculate pointer to the rightmost parameter
mov r1,a
mov a,@r0
add a,@r1
add a,r2 ;calculate the result (= sum of all three parameters)
mov dpl,a ;return value goes into dptr (cast into int)
mov dph,#0x00
mov sp,_bp
pop _bp
ret