Index Home About Blog
From: Chris Torek <torek@elf.bsdi.com>
Newsgroups: comp.lang.c.moderated
Subject: Re: Variable param for non-vararg subr. calls
Date: 2 Mar 1996 13:38:54 -0600

(This article is a bit of a mishmash, but mostly tries to illustrate
some of the possible calling conventions.)

On Fri, 1 Mar 1996, I wrote (in private mail -- hey, ask before you
post! :-) ):
>> (The sparc calling convention passes structures specially -- that is,
>> the call sequence for an aggregate is different from that for any
>> scalar.)

In article <4h9mjl$grs@solutions.solon.com>
Jens M Andreasen <jens-and@dsv.su.se> writes:
>Ok!
>
>Is there a workaround for this? (No, forget it ... We have just bought a 
>bunch of them. I'll have a look at it, maybe ..)

To expound a bit: on the sparc, a function that appears to receive
a struct or union actually receives a pointer to that struct or union.
A caller who passes a struct or union must first copy the value of
the aggregate into a temporary, then pass a pointer to that temporary.

This means that the (incorrect) code:

	void f(p) struct X *p; {
		... use but do not change p->elem ...
	}

	void g() {
		struct X val;
		...
		f(val);
	}

will actually `work' on the sparc (a bit expensively since g() will
copy `val' to a temporary).

In effect, `small' arguments go in envelopes (registers), and `large'
arguments go in boxes (memory) with a slip inside an envelope telling
where to find the box.

>> Your code will also fail on MIPS-based systems, among others.

>This confuses me a bit since I usually jack into a DecStation having (to 
>my knowledge) some Mips inside ?? The sample output was taken from there.

I probably goofed here.  The MIPS passes the first four 32-bit
integer arguments in the `a' registers (a0, a1, a2, a3, also known
as $4 through $7).  I believe, but cannot verify, that the first
two `double' arguments are also passed via registers, in this case
$f0 and $f2.  If the calling convention for struct arguments is to
break them down element-wise and pass each element in the corresponding
argument slot, the technique would work in all cases.  On the other
hand, if the calling convention is to break them down into four-byte
blobs and pass them as if they were integers, the technique might
fail when the first or second argument should have type `double'.

I do not know if the MIPS calling conventions identify (and then
treat differently) variable-argument functions, and my ancient Kane
book (copyright 1988) does not go into details.  (The example code
provided a `double' argument, but it was the third argument to
printf(), so the above represents my best guesses as to the MIPS
conventions for struct arguments.)

>While I'm at it: The code do run on 68k and Intel based systems :-)

I am not sure if Intel have ever provided specific guidelines as
to calling conventions on the 80x86 platforms, but there are only
two major variants of which I am aware -- these being the so-called
`cdecl' and `pascal' conventions.  It is, however, worth noting
that any compiler that uses `pascal conventions' for fixed-argument
functions and `cdecl conventions' for variable-argument functions
could also cause this code to fail (because, e.g., a struct is a
fixed argument, and puts takes a fixed argument, but the number of
bytes of arguments between the two are unbalanced, so that the
`ret' from puts() would pop the wrong number of argument bytes;
whether this was quickly fatal would depend on whether the compiler
used a frame pointer).

680x0 platforms have also used various calling conventions, but the
most widespread variants all pass all parameters by pushing right to
left on sp@- and return all values in d0.  A few compilers have
returned pointer values in a0.  This means that you must distinguish
between `functions returning pointers' and `all others', but it does
not affect argument-passing.

Possibly the toughest calling convention would occur on a machine
that has two or more `flavors' of register, and passes arguments in
registers.  In this case, you might find that:

	void f1(int, char *)
	void f2(char *, int)

both used the same call!  Here the integer might go in d0, and the
address in a0.  Such a convention would be useful on (surprise :-) )
the 680x0.
-- 
In-Real-Life: Chris Torek, Berkeley Software Design Inc
El Cerrito, CA	Domain:	torek@bsdi.com	+1 510 234 3167

From: torek@elf.bsdi.com (Chris Torek)
Newsgroups: comp.lang.c
Subject: Re: Interlanguage calls.
Date: 2 Feb 1997 17:08:59 -0800

Note: most of this article is on parameter-passing mechanisms.
There is a bit at the end on `confusion'. :-)

In article <32F4AD60.53E5@ici.net> Alicia Carla Longstreet <carla@ici.net>
writes:
>There are basically two ways to make parameters available to the called
>routine.

Actually, there are dozens that have been used in practice (see my
other followup).  Most reasonably modern architectures pass the
first few (typically two to eight) parameters in registers, e.g.,
$a0 through $a3 or %o0 through %o5 (floating point parameters
should go in the FP registers, but Sun defined the SPARC calling
convention to pass them in the integer registers, so of these two
examples, only the MIPS does it `right').  The PowerPC has at least
three different ABIs defining calling conventions for it; they all
pass integer parameters in r3 through r10.  (If the object being
passed is not a scalar, the various ABIs all have different setups
for who copies what.)

Additional arguments, if any, are typically placed in some memory
location relative to some specific register (e.g., a frame or stack
pointer).

>Using a stack for the example, you can push the first parameter
>first and work your way to the last, or you can push the last
>first and work back to the first.  The calling routine, of course,
>retrieves the parameters in reverse order.

This is of course true, and is the `accepted' ABI on (e.g.) the
VAX (though, as should not be surprising, you can often get better
performance by passing parameters in registers there too).  The
VAX, however, has a dedicated `argument pointer' register (R12,
aka AP), and you can put your parameters anywhere you like and
use the `callg' instruction rather than the `calls' instruction.
None of which is particularly relevant to:

>Now maybe I am wrong, but the last parameter first method, allowing the
>calling routine to retrieve the first parameter first, is the simplest
>way to implement variable argument lists.

On machines with a natural downward-growing stack, and an ABI that
(for better or worse -- usually worse) calls for all parameters to
be placed on the stack, this is in fact what C compilers typically
do, because it works in a `natural' way:

>This way, the first (or any of the 'known' parameters) can be used
>to specify how many parameters there are.

More specifically, if all the parameters are on the stack and in
sequential memory locations, then:

	(char *)((&last_fixed_arg) + 1)

is (modulo alignment constraints) the address of the first of the
varying arguments.  Note that we have, however, assumed:

	- there is a stack
	- arguments are passed on the stack
	- arguments are sequential

none of which is required by the C Standard.

>If you push the parameters first parm first, how can you implement
>variable parameters.  How can the called routine ever know how
>to retrieve the values.

Well, imagine again that we have the same set of constraints as
noted above.  Imagine further that our variable-argument function
looks just like printf(), and that it is coded as:

	#include <stdarg.h>
	#include <stdio.h>

	extern FILE *logfp;	/* opened elsewhere */

	int log(const char *fmt, ...) {
		va_list ap;
		int ret;

		va_start(ap, fmt);
		ret = vfprintf(log, fmt, ap);
		va_end(ap);
		return ret;
	}

What kind of magic must <stdarg.h> and va_start() use to find the
varying arguments, and how will the compiler locate the fixed
argument `fmt'?

Since the function must be defined via prototype syntax, and all
calls must be made with a prototype in scope (ANSI Classic, sections
3.5.4.3 and 3.7.1), the implementation can call such a function
with an extra `hidden' argument passed after the last variable
argument; that extra argument can be a pointer back to the first
argument, or the first varying argument.  Although this takes
extra space, it might provide compatibility with a compiler for
another language (such as Pascal or Fortran) supplied by the same
implementor.

Alternatively, a compiler could choose to push parameters `left to
right' for all fixed-argument functions, and `right to left' for
all varying-argument functions.  This would, for instance, provide
compatibility between existing C and Pascal compilers on existing
DOS-based IBM PC systems.  This would be an example of how an
implementation would use the new (as of ANSI C) syntax for
varying-arguments functions to treat them differently from all
other functions.  (For some reason, PC compiler vendors have not
chosen to do this, but instead have added `__pascal' keywords or
similar.  I can *guess* why, but since I am not such a vendor, I
cannot say for certain.)

Now on to the confusion bit:

>Given that the ANSI C Standard permits variable length argument lists,
>they are pretty much specifying calling syntax.

Whoa, hold on a moment here.  When most people here in comp.lang.c
talk about `syntax', we mean (or SHOULD mean) `what C programs look
like'.  The Standard defines the language in terms of its syntax
(acceptable strings of keywords, variable names, braces, semicolons,
etc.) and its semantics (the *meaning* of any given construct).
The `calling syntax' is simply:

   function-designator  left-parenthesis  argument-list  right-parenthesis
	e.g.	   foo		(		3		)

ANSI C certainly does define the syntax and semantics for calling
a function with varying arguments.  However, everything above the
`confusion' remark deals with *implementations* -- ways that we
can get the machines we have to implement the required semantics
for programs written in the accepted syntax.  The Standard itself
says nothing about how this is to be achieved; a system in which
you (for instance) dance the Macarena in front of a source listing,
after which the required semantics have mysteriously been achieved,
could be conformant.

>I suppose a compiler could use a first parm first for fixed length
>lists, and last parm first for variable length lists,

Yes, exactly.  According to the minutes of the meetings, this
particular idea is one reason for the constraints in 3.5.4.3 and
3.7.1.

>but I suspect that, besides making it harder for the programer
>who is mixing languages, it would be harder implement.

I think it would be easy enough to implement.  I am curious, though,
as to why you suspect it might be harder for programmers mixing
languages.

>Show me that you can implement variable length argument lists, with a
>stack (most common method), that allows first argument pushed first.
>Show me that compiler makers routinely use different calling conventions
>for different functions.
>Then I will agree that the ANSI Standard does not 'define' a calling
>syntax for C.

I think this is a bit absurd, but if I show you GCC for the PowerPC,
will that suffice?  It uses different calling conventions for
different functions; in fact, it even uses different calling
conventions for the *same* functions, depending on which ABI you
tell it to use.  On the other hand, it passes the first 8 integer
arguments in registers, rather than on the stack (though it *does*
use a stack).
--
--
In-Real-Life: Chris Torek, Berkeley Software Design Inc
El Cerrito, CA	Domain:	torek@bsdi.com	+1 510 234 3167
Antispam notice: unsolicited commercial email will be handled at
	my consulting rate of $200/hr.

From: torek@elf.bsdi.com (Chris Torek)
Newsgroups: comp.lang.c
Subject: Re: Why C's superiority
Date: 8 May 1998 00:31:44 -0700

[Names filed off, partly out of laziness. :-) ]

>>By the way, are you saying that ANSI C can automatically select the calling
>>mechanism depending on the parameter types ?

>ANSI C doesn't select any calling mechanism. That's an implementation
>detail it doesn't concern itself with.

More specifically, implementations are *allowed* to do just that
-- to select the calling mechanism depending on the parameter types
-- but are not *required* to do so.

Compilers for machines using the MIPS R2000, R3000, R4000, etc.,
architecture generally do this.  On these machines, the first four
non-floating scalar parameters are passed in the four "argument"
registers a0, a1, a2, and a3; the first few floating-point parameters
are passed in the first few FP registers; non-floating scalar return
values are returned in a0; floating point return values are returned
in floating point registers.  Thus:

	double square(double x) { return x * x; }

does not have to move the floating-point argument "x" anywhere
special before squaring it, and does not have to move the result
anywhere special after squaring it.

Calls to variable-argument functions such as printf(), however,
pass *all* arguments, including floating point arguments, in the
"argument" registers.  If we rewrite the above function as:

	double silly_square(int dummy, ...) {
		va_list ap;
		double x;

		va_start(ap, dummy);
		x = va_arg(ap, double);
		va_end(ap);
		return x * x;
	}

the caller must copy the floating point bit-pattern into the integer
argument registers, and silly_square() must extract the original
floating point value into the floating point registers, square it
there, and return it in the floating point registers.

(If there are more than four scalar arguments, or too many -- I am
not sure offhand how many "too many" is here -- floating point
arguments, excess arguments are placed on the stack.  The MIPS
stack frame format is complicated -- there is a frame pointer in
each stack frame if and only if the frame has variable size;
otherwise you designate a "virtual frame pointer" as a constant
offset from the nominal "stack pointer" register -- and I am not
sure how this detail works either; I have ever written simple
functions in MIPS assembly, starting from ".s" files generated by
a C compiler.)

In any case, this means that, on a MIPS system, a call to printf()
printing floating point values may well fail mysteriously if the
programmer has forgotten to "#include <stdio.h>".

I think it is interesting to compare this particular calling sequence
with that for another RISC architecture, the (V8) SPARC.  On the
SPARC, the first six scalar parameters are passed in the six integer
"out" registers %o0 through %o5.  This includes floating-point
parameters, and a simple function like:

	double ident(double x) { return x; }

winds up compiling to code that stores %o0 and %o1 to memory, then
loading them into %f0 and %f1 for return.  This means that variadic
functions are called in the same way as non-variadic functions.
(Extra arguments are still placed on the stack, but %o6/%sp is
always a stack pointer, %i6/%fp is always a frame pointer, and the
extra arguments are always at [%fp+92].  Moreover, the caller's
space from [%fp+68] up to [%fp+92] is reserved for the callee (!).
This is then used in a tricky fashion: %o0 through %o5 are written
at [%fp+68], [%fp+72], ..., [%fp+88], and a "va_list" is then just
a pointer to the space beginning at %fp+68.)

This means that same programmer, with that same bug, will see his
code work on a SPARC box.  Unfortunately, all other programmers
that pass floating-point values will, in general, see *all* their
code run more slowly than necessary, because the compiler keeps
moving values between the integer and floating-point registers
(which requires stores and loads to memory).

Interestingly, the V9 version of the SPARC GCC uses a call mechanism
more like that of the PowerPC ABI, in which floating point parameters
are passed in the floating-point registers, even to variadic
functions.  On these machines, a "va_list" object contains
the following data:

	a count or index for "integral arguments" +
		(a pointer to) the integral arguments
	a count or index for "floating arguments" +
		(a pointer to) the floating arguments
	a count or index or pointer for "overflow arguments"

On machines with special pointer registers (separate from integer
registers), it would often make sense to pass pointer parameters
in the pointer registers, and expand the va_list object to include
those as well.

A summary (for those that have made it this far):

 - SPARC V8 systems pass all parameters identically, to the detriment
   of performance but giving "bug compatibility" with K&R C.

 - SPARC V9 systems pass all parameters identically as well, but
   differently from V8; this improves performance, yet retains the
   "bug compatibility", and merely needs a compiler hook to get
   the arguments stored correctly, and to implement "va_arg".

 - MIPS systems change the call sequence for variadic functions --
   no "bug compatibility" (you must declare functions correctly)
   but a simple implementation of "va_arg".
--
In-Real-Life: Chris Torek, Berkeley Software Design Inc
El Cerrito, CA	Domain:	torek@bsdi.com	+1 510 234 3167
Antispam notice: unsolicited commercial email will be handled at my
consulting rate; pyramid-scheme mail will be forwarded to the FTC.

Index Home About Blog