Work at SourceForge, help us to make it a better place! We have an immediate need for a Support Technician in our San Francisco or Denver office.

Close

#253 Register parameter passing

open
nobody
z80 port (29)
5
2008-12-27
2008-03-20
No

Similar to RFE #979838, but #979838 is for mcs51, while this one is for Z80.

Passing arguments in registers would reduce call overhead.
However this makes sense for small parameters only: Any register pair can be pushed by the caller, but if the arguments are passed in registers that could mean that we'd have to move them around in registers a lot before the call. In a simialr way the callee would probably spend a lot of time reordering arguments, unless we take the registers used for arguments away from the register allocator.
Using the second register set is probably quite complicated.

I see two possible solutions:
*Use register arguments for functions where the sum of the arguments' sizes is below 24 bits only. We could then use a, h, l, which are not used by the register allocator for arguments.
*Let the user decide. The C standard allows use of the register storage class for function parameters.

Passing arguments in registers would mostly help with small function, which is currently one of sdcc's weak points.

Philipp

Discussion

1 2 > >> (Page 1 of 2)
    • labels: --> z80 port
     
  • Steps needed:

    - Make the notUsed() peephole function aware of passing parameters in registers, so assignments to register arguments are not considered dead code. This has to be done exactly, since notUsed() is probably the peephole optimizer's single most powerful tool, and it's effectiveness should not be compromised.

    - Fix bug #2811521.

    - Enable register parameters by changing the default value of --no-reg-params to 0 for the Z80 port.

    - This solution would use de and bc for passing parameters that have the register storage class specifier.

    Philipp

     
  • I reently learned that the C stnadard ignores storage class specifiers for the parameters in function declarations, they matter only in the definition. Thus, we cannot do the passing in regiasters depending on the storage class specifier. That makes this feature request unlikely to get implemented soon, if at all.

    Philipp

     
  • b-s-a
    b-s-a
    2010-11-11

    Is it possible add feature of using specified registers as arguments? It is useful for external functions written on assembler.
    For example, I have set of asm procedures which takes parameters: IX - some address, HL - some other address/data, B, C, D, E, returns IX, uses all, except AF, BC, DE, HL, IX. So I want to use these functions (they are time critical) without wrappers. For example:
    extern uint16_t* asm_func(uint16_t*, const uint16_t*, uint8_t, uint8_t) __naked(IX : IX, HL, B, C : AF, BC, DE, HL, IX);

     
  • b-s-a
    b-s-a
    2010-11-11

    *fix:
    ... returns IX, uses AF, BC, DE, HL, IX. ...

     
  • alvin
    alvin
    2010-11-11

    As Philipp mentioned, the z80's small number and non-orthogonal instruction set means placing parameters in arbitrary registers prior to a call can involve a lot of overhead that would make it detrimental in most cases involving more than 2 or 3 parameters. However there are two calling linkages that we have found greatly improves z80 code: (1) CALLEE linkage where the callee is responsible for stack cleanup and (2) FASTCALL linkage where a small number of parameters (1 or 2) are passed in [DE]HL. The latter is consistent with how values are returned out of functions in all z80 C compilers I have found (specifically longs are returned in DEHL).

    We've used both for library code and supply an additional asm entrypoint in library functions using CALLEE linkage for asm programmers to sidestep the register initilizaition in function calls. Here is an example of both:

    CALLEE linkage:

    "
    (CALLEE linkage assumes left to right -pushing of params on stack)
    ; long __CALLEE__ strtol(const char * restrict nptr, char ** restrict endptr, int base)

    XLIB strtol, asm_strtol
    ; export C and asm entrypoints

    strtol:

    pop hl
    pop bc
    pop ix
    ex (sp),hl

    ; enter:
    ; bc = base
    ; ix = char **endptr
    ; hl = char *nptr
    ;
    ; exit:
    ; dehl = result (could be LONG_MAX or LONG_MIN on overflow)
    ; bc = address of next char to examine in nptr[]
    ; carry = error (overflow, bad base, empty conversion string)
    ; errno set to ERANGE (overflow), EINVAL (bad base / empty conversion string)
    ; *endptr set appropriately
    ;
    ; uses:
    ; af, bc, de, hl, af', bc', de', hl', ix

    asm_strtol:
    ........ body continues
    "

    compiler calls like so:

    ; parameter collection in hl
    push hl ; nptr
    ; parameter collection in hl
    push hl ; endptr
    ; parameter collection in hl
    push hl ; base
    call strtol
    ; note no stack cleanup, this adds up quickly and saves a lot on code size

    An example of FASTCALL linkage (incoming parameter always in [DE]HL)

    "
    ; char *strrev(char *s)
    ; reverses string s
    ;
    ; enter:
    ; hl = char *s
    ;
    ; exit:
    ; hl = char *s
    ;
    ; uses:
    ; af, bc, de

    strrev:
    .... body continues
    "

    compiler calls like so:

    ld hl,parameter
    call strrev

    The C and asm entrypoint is shared.

     
  • b-s-a
    b-s-a
    2010-11-12

    As I said before there is some cases where it is required to call external function, passing parameters in registers.
    Currently to do call to my functions I need use inline assembler with lot of overhead:
    1. store IX and other registers
    2. load BC
    3. load HL
    4. load IX (it may be difficult)
    5. do call
    6. restore IX and other registers

    I do not know which registers I should save at any time. It is compiler's job, isn't it?

     
  • alvin
    alvin
    2010-11-13

    You misunderstand, I agree with you :) The way things are now in sdcc-z80 is not adequate for adding external asm functions, including library functions. I am just proposing alternatives to what you suggested.

    The idea of specifying parameters to be placed in certain registers will not work well IMO unless it is very few parameters (ie one, two, *maybe* three). This is because of all the gymnastic the compiler would have to perform to compute parameter values and get them into the right registers prior to call. This cost is paid in code size every time the external asm function is called. Also, I would guess that parameters would have to be temporarily saved to the stack prior to final register set up quite frequently which would also make it slower than the CALLEE alternative I suggested.

    The CALLEE alternative has the compiler push parameters onto the stack and the external asm function pop them into the correct registers. The caller (ie compiler) does not have clean up the stack afterward as the external asm function does that itself when popping parameters into registers.

    The FASTCALL alternative I mentioned works with passing a limited number of parameters in DE,HL. It could be one parameter in HL (16 bit), one in DEHL (32 bit) or two 16-bit in DE,HL. This would be the passing params in registers thing you are requesting but only for one or two params. This places essentially no overhead on the compiler because quite frequently a parameter will be computed in DE,HL for the call. The external asm function quite frequently wants the one or two parameters in DEHL (due to the nature of the z80 instruction set!) so it is almost a clean transfer of program flow without the compiler needing more information about the internals of an external function.

    Lastly, I agree with it being the compiler's job to ensure its temporaries are saved prior to a call. I don't know if it does that now or if a new CALLER-save qualifer needs to be introduced to tell the compiler to save its temporaries.

    So my proposal is add: CALLEE, FASTCALL linkage and CALLER-save qualifier if necessary. It is just not practical to add external asm functions and even libraries without them.

    Longer term, it may be advantageous to pass some metadata about the external function to the compiler such as which registers are actually destroyed so the compiler can make more intelligent decisions about placement of the function call and what temporaries actually need to be saved but this is something that would need a lot of work. I would also like to see sdcc-z80 get away from assigning roles to registers and using ix as a stack frame altogether but that is also a very long term project if it ever happens... there is a reason why expert z80 programmers almost never use ix as a frame pointer in their hand coded assembler :)

     
  • b-s-a
    b-s-a
    2012-03-10

    aralbrec, your suggestion is good for large number of passing data. Because data is always pushed to the stack and popped back. My suggestion is not optimal too, but it cause simpler use of external libraries...

    spth, is there any progress?

     
  • b-s-a
    b-s-a
    2012-03-10

    Also it is possible to pass first argument (if it pointer to variable of complex type (struct/union) only) in IY.

    void memcpy(void*, void*, uint16_t) will accept: HL, DE, BC
    void process(struct MyStruct *ms, uint16_t, uint16_t) will accept IY, HL, DE

     
1 2 > >> (Page 1 of 2)