mvac7 / SDCC_MSX_fR3eL

La Fril, mola mil! (WORK IN PROCESS)
11 stars 1 forks source link

Adapt code to new SDCC (4.1.12) Z80 calling conventions #5

Open mvac7 opened 10 months ago

mvac7 commented 10 months ago

References

4.3.3 Z80, Z180, Z80N and R800 calling conventions

The current default is the SDCC calling convention, version 1. Using the command-line option –sdcccall 0, the default can be changed to version 0. There are three other calling conventions supported, which can be specified using the keywords smallc, z88dk_fastcall and z88dk_callee. They are primarily intended for compatibility with libraries written for other compilers. For __z88dk_fastcall, there may be only one parameter of at most 32 bits, which is passed the same way as the return value of sdcccall(0). For z88dk_callee, the stack is not adjusted for stack parameters the parameters after the call (thus the callee has to do this instead). __z88dk_callee can be combined with smallc, sdcccall(0) or sdcccall(1).

4.3.3.1 Z80 SDCC calling convention, version 1

This calling convention can be chosen per function via __sdcccall(1). 8-bit return values are passed in a, 16-bit values in de, 24-bit values in lde, 32-bit values in hlde. Larger return values (as well as struct and union independent of their size) are passed in memory in a location specified by the caller through a hidden pointer argument.

For functions that have variable arguments: All parameters are passed on the stack. The stack is not adjusted for the parameters by the callee (thus the caller has to do this instead). For Functions that do not have variable arguments: the first parameter is passed in a if it has 8 bits. If it has 16 bits it is passed in hl. If it has 32 bits, it is passed in hlde. If the first parameter is in a, and the second has 8 bits, it is passed in l; if the first is passed in a or hl, and the second has 16 bits, it is passed in de; all other parameters are passed on the stack, right-to-left. Independent of their size, struct / union parameters and all following parameters are always passed on the stack.

mvac7 commented 10 months ago

Input: two 8-bit parameters Output: 8-bit value

char aFunction(char param1, char param2) __naked
{
param1;  //A
param2;  //L
__asm
  ld   B,A  ;param1
  ld   A,L  ;param2

LOOP01:
  inc  A
  djnz LOOP01

  ret    ;return A
__endasm;
}

Input: 16-bit value Output: ---

void aFunction(uint param1) __naked
{
param1; // HL

__asm
  ex    DE,HL  ;exchange DE <-->HL

  ret
__endasm;
}

Input: two parameters: 8-bit and 16-bit Output: 8-bit value

char aFunction(char param1, uint param2) __naked
{
param1; // A
param2; // DE

__asm
  ld   B,A
  ld   A,(DE)

  add  A,B 

  ret   ; return A

__endasm;
}

Input: two parameters: 16-bit and 16-bit Output: 16-bit value

uint aFunction(uint param1, uint param2) __naked
{
param1; // HL
param2; // DE

__asm
  add  HL,DE
  ex    DE,HL

  ret    ; return DE
__endasm;
}

Input: two parameters: 16-bit and 8-bit Output: --

void aFunction(uint param1, char param2)
{
param1; // HL
param2; // stack

__asm
  push IX
  ld   IX,#0
  add  IX,SP

  ld   A,4(IX)  ;takes the value of the second parameter from the stack

  ld   (HL),A

  pop  IX
__endasm;
}

Input: three 8-bit parameters Output: 8-bit value

char aFunction(char param1, char param2, char param3)
{
param1; // A
param2; // L
param3; // Stack

__asm
  push IX
  ld   IX,#0
  add  IX,SP

  ld   B,4(IX)  ;takes the value of the second parameter from the stack
loop$:
  add  A,L
  djnz loop$

; output value in A 

  pop  IX
__endasm;
}
mvac7 commented 10 months ago

The use of __naked is not recommended in functions that receive parameters through the stack, since it does not add the code that positions the stack.

For a function that returns an 8-bit value:

char Function8(unsigned int var16, char var8, unsigned int var2_16)
{
var16;      //HL
var8;       //Stack
var2_16;    //Stack

__asm
  push IX
  ld   IX,#0
  add  IX,SP

  ld   E,5(IX)
  ld   D,6(IX)

  ld   A,4(IX)
  ld   (HL),A
  inc  HL
  ld   A,(DE)

  pop  IX
__endasm;
}

The compiler generates the following code:

; ---------------------------------
; Function Function8
; ---------------------------------
_Function8::
;src\Test.c:339: __endasm;
    push    IX
    ld  IX,#0
    add IX,SP
    ld  E,5(IX)
    ld  D,6(IX)
    ld  A,4(IX)
    ld  (HL),A
    inc HL
    ld  A,(DE)
    pop IX
;src\Test.c:340: }
    pop hl
    pop bc
    inc sp
    jp  (hl)

For a function that returns an 16-bit value:

unsigned int Function16(unsigned int var16, char var8)
{
    var16 += var8;

    return var16;   
}

The compiler generates the following code:

; ---------------------------------
; Function Function16
; ---------------------------------
_Function16::
    push    ix
    ld  ix,#0
    add ix,sp
;src\Test.c:327: var16 += var8;
    ld  c, 4 (ix)
    ld  b, #0x00
    add hl, bc
    ex  de, hl
;src\Test.c:329: return var16;  
;src\Test.c:330: }
    pop ix
    pop hl
    inc sp
    jp  (hl)
mvac7 commented 10 months ago

Important!

If you use the IX register, you must save it at the beginning and retrieve it at the end of your function, since SDCC can use it to point out where the values of local variables are located.