Subsections


4.1.5 Interfacing with Assembler Code


4.1.5.1 Global Registers used for Parameter Passing

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.

4.1.5.2 Register usage

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.

4.1.5.3 Assembler Routine (non-reentrant)

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); 
 
int c_func (unsigned char i, unsigned char j) 
{ 
    return asm_func(i,j); 
} 
 
int main() 
{ 
    return c_func(10,9); 
}
The corresponding assembler function is:
        .globl _asm_func_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
The 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.

Assemble the assembler routine with the following command:

sdas8051 -losg asmfunc.asm

Then compile and link the assembler routine to the C source file with the following command:

sdcc cfunc.c asmfunc.rel

4.1.5.4 Assembler Routine (reentrant)

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; 
 
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); 
}
The corresponding (unoptimized) assembler routine is:
.globl _asm_func 
_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
The 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.