[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [f-cpu] calling conventions

----- Original Message -----
From: Michael Riepe <michael@stud.uni-hannover.de>
To: <f-cpu@seul.org>
Sent: Thursday, June 06, 2002 1:45 AM
Subject: Re: [f-cpu] calling conventions

> As I mentioned here before, a function is free to use the unused
> parameter registers as local (unsaved) temporary registers.
> > Moreover there is a stack of register for each calls, which want to say
> > for calling a function we need 3 stages:
> >  1/ put the 13 parameters in the registers r1-r13
> >  2/ push all the registers in r14 and put their number in r15
> Not all of them - just the rest. Since there are few function calls
> with 14 or more parameters, this is quite a rare case. There are some
> examples, however - like long calls to printf()/scanf().

I see a difficulty : printf and scanf and likes use varargs, that is something
which is pushed in stack and accessible via a pointer :

Here an example such you can find for PC :

va_start (fmt,arg) --> arg = (void *)((typeof(fmt))+1);

va_arg(arg,type) --> *((type *)arg)++;


Now, since printf or scanf use registers for variable arguments, how do you
plan to access them throughout va_arg ??? especially in such situation :

    while (*s) switch (*s) {
        case '1' : write_le_u8 (out,va_arg(arg,unsigned char)); break;
        case '2' : write_le_u16 (out,va_arg(arg,unsigned short)); break;
        case '4' : write_le_u32 (out,va_arg(arg,unsigned int)); break;

As you can see, the compilator cannot guess which register it could use at
the only solution is to push them in memory at run-time before using them.

Personally, I think the best we can do when doing a printf is to push all fix
parameters in register and optionnal parameters in stack, so :

printf (r1:char *fmt,stack:r15 ...);

sprintf (r1:char *str,r2:char *fmt,stack:r15 ...);


Should we try to push variable arguments into registers, the only alternative
is to have something like in pseudo-c :

print (r1,r2 ...) {
    int i = 2; int v;
    while (*s) switch (*s) {
        switch (i++) {
            case 2: v = r2;
            case 3: v = r3;
            case 4: v = r4;
            case 14: v = r14;
            default: v = ((int *)r15)[i - 2];
        case '1' : write_le_u8 (out,(unsigned char)v); break;
        case '2' : write_le_u16 (out,(unsigned short)v); break;
        case '4' : write_le_u32 (out,(unsigned int)v); break;

Now just imagine how to code var_arg that way... you will forced to handle that
complicated switch code for each var_arg encountered : every long and ugly
code. I'm not sure it is worth that effort.

> > What I propose has the place : first an observation, as well the caller
> > function as the function to call know at the compile time the number of
> > parameters.
> Not true. In some languages, there are functions taking a variable
> number of arguments - which is of course unknown when the function is
> compiled.

Let us be precise :) :
- caller side : it DOES know the number of parameters so it can tell to the
callee via a dedicated register. ==> COMPILE-TIME.
- callee side : it DOESN'T know the number of parameters but it can get it if
the caller tells this number to it. ==> RUN-TIME.

Now, is knowing this number necessary for callee ? well, it depends on what the
callee really needs.

> > In all the cases the register r1 is used (by the number of parameters), it
> > thus available to store the value of return of the function.  The number of
> > parameters was present, same if it is known of the two functions, in order
> > be able to possibly make checks.
> You can always store the result in r1, whether it's the first argument
> or the number of arguments. Since you usually do so at the *end* of a
> function, it makes no difference.

Advantage :

r1:int addi (r1:int a,r2:int b) {
    return a + b;

instead of :

r1:int addi (r2:int a,r3:int b) {
    return a + b;

will give us :

r1 += r2;

instead of :

r1 = r2 + r3;

and save r3 for temporary register.

Another advantage :

struct { r1:int error; r2:struct open_file *file; } file_open (r1:char

You can have not only one register as result but several registers this way !

> > In the case of more than 13 parameters I don't know if it is useful to put
> > the parameters in the list pointed by r15. Indeed to have all the
> > lists some in very practical, for the function such as [printf] in C or
> > [format] in Pascal, but I am not sure time to gain in these function is a
> > superior than time wasted to put in memory parameters which are already in
> > registers.
> You're right again (and the manual is wrong wrt. this point).

See above

> r0      = always zero
> r1      = function return value, and also
> r1-r14  = function arguments (call-clobbered)
> r15     = pointer to additional function arguments (call-clobbered)
> r16-r31 = temporary registers (call-clobbered)
> r32-r47 = local registers (saved by callee)
> r48-r63 = global registers (shared by all functions)
> with the following assignments for C- and Pascal-style languages (but
> not necessarily others):
> r60 = function return address
> r61 = global pointer
> r62 = frame pointer
> r63 = stack pointer
> Note that r48-r63 are *not* callee-saved as the manual states. A function
> *must not* expect them to be unchanged across function calls (but *may*
> save/restore them on entry/exit and use them like the callee-saved
> registers r32-r47 if it doesn't need the global values).  Analogously,
> a function *may* use r1-r15 as temporary registers if there aren't so
> many arguments (or it doesn't need them).

I don't see the purpose to have so many global registers (shared by all
functions). What can really be shareable between all functions ? very few
indeed... unless it should be for kernel but even for such a thing it is a very
bad idea because any applications might access or modify them.

There is another point else which I would enlight : what about the ability to
use a pair of registers to have 128 bit when 64 bits is default ? especially
for such opcodes which use or return  a pair of register (Rn,Rn^1) ? not very
good if the first parameter or return register starts from r1. Worse, what can
happen if we use an OPCODE which uses or computes a pair of register (Rn,Rn^1),
especially when Rn == R1 ? Rn^1 would be R0, but R0 is hardwired to 0 !

To unsubscribe, send an e-mail to majordomo@seul.org with
unsubscribe f-cpu       in the body. http://f-cpu.seul.org/