Index
Home
About
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.
Index
Home
About