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

Re: [f-cpu] calling conventions



On Thu, Jun 06, 2002 at 10:56:36AM +0200, Christophe wrote:
[...]
> > 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.

When we don't put them on the stack, va_start/va_arg will have to do a
little more work, that's right. Is that a problem? I want my `regular'
functions to run fast!

> 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.

That will be the task of va_start(). In fact it is the reason why
va_start() and va_end() exist at all! If you could be sure that the
parameters are always on the stack and the stack always grows down,
you could simply say `ap = (void**)(&last_fixed_arg + 1);'. Or maybe
`ap = &...;', as they do in some compilers that treat `...' like an
identifier.

I expect va_start() to look like this on the F-CPU:

	// prologue of calling function
	subi $16*8, r63, r63	// allocate space on stack
	move r63, r17			// let r17 hold `ap' (arbitrary choice)
	...
	// (inline) call to va_start()
	loadconsx $<number_of_fixed_args>, r16	// known at compile time
	storem r1, [r17], r16	// save r1-r16 (syntax still unclear)
	...
	// epilogue of calling function
	addi $16*8, r63, r63	// restore stack pointer

That's all!

va_arg() in turn will not change `ap' but the value stored in ap[15]. Let
`WORD' be the type corresponding to a machine word, then va_arg(AP, TYPE)
becomes (for sizeof(type) <= sizeof(WORD)):

	WORD *pointer = (WORD*)AP;
	WORD i = AP[15];
	WORD tmp;

	if (i >= 14) {
		/* this is the only tricky part */
		pointer = (WORD*)AP[14] - 14;
	}
	tmp = pointer[i];
	AP[15] = i + 1;
	return (type)tmp;

In case an argument is longer than a word (e.g. structure arguments),
execute this sequence repeatedly for the individual words of the
argument (there is also a more efficient solution, but I'm too tired
to write it down ;).

You see, it's no problem at all. There is a little overhead when dealing
with variable argument lists, but that's ok as long as functions with
a fixed number of arguments run fast.

> 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.

No. All functions MUST use the same calling conventions, whether they
take a variable number of arguments or not. (The C99 standard does not
require it - in fact, it contains rules that avoid this situation -
but there is a *lot* of C code out there that won't work otherwise).

[...]
> 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 !

That's another valid option. The canonical way to return structures is
different however: the function

	struct blah func(...) {
		struct blah x;
		...;
		return x;
	}

is turned into

	struct blah *func(struct blah *result, ...) {
		struct blah x;
		...;
		*result = x;
		return result;
	}

The advantage is that `struct blah' can be arbitraryly large. We can
(and probably should) define that small structures (up to 15 words)
are returned in registers, the way you proposed it.

[...]
> 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.

You overlooked that each application (and also the kernel) will have
its own register set. At least if we're talking about *real* operating
systems ;)

You can use registers for any global variable in any application.
E.g. inside the Linux kernel, you would use registers for `current',
`jiffies' and probably the addresses of some often-used memory locations
(like the task table, for instance). In a Forth interpreter, you can
use global registers for the stack pointers, the instruction pointer,
HERE and probably some well-known addresses. In C, you'll have stack and
frame pointers, a pointer to the top of the heap, pointers to often-used
global data (stdin/stdout/stderr/errno for example) and so on.

How many examples do I have to list in order to convince you? :)

> 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 !

You'll have to move the values around a little, then. E.g. if you turn
`mulh' into a function, it will look like this:

	mulh r1, r2, r3	// high part written to r2
	move r3, r1
	jmp r60

But in most cases, you'll use inline code anyway (and save both the
move and the jmp).

-- 
 Michael "Tired" Riepe <Michael.Riepe@stud.uni-hannover.de>
 "All I wanna do is have a little fun before I die"
*************************************************************
To unsubscribe, send an e-mail to majordomo@seul.org with
unsubscribe f-cpu       in the body. http://f-cpu.seul.org/