[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
that
> > 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)++;
etc.
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
compile-time...
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 ...);
etc.
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
is
> > 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
to
> > 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
*name,...);
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
all
> > the parameters in the list pointed by r15. Indeed to have all the
parameters
> > 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
the
> > 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/