Needed: A (Portable) way of setting up the arg stack

Steve Summit scs at adam.pika.mit.edu
Mon Jun 5 02:43:52 AEST 1989


In article <708 at mitisft.Convergent.COM> kemnitz at mitisft.Convergent.COM (Gregory Kemnitz) writes:
>I need to know how (or if) *NIX (System V.3) has the ability to let
>a stack of arguments be set for a function before it is called.  I
>have several hundred pointers to functions which are called from one
>place, and each function has different numbers of arguments.

A nice problem.  Doug Gwyn's suggestion is the right one, for
maximum portability, but constrains the form of the called
subroutines and also any calls that do not go through the
"inverse varargs mechanism."  (That is, you can't really call the
subroutines in question without building the little argument
vector.)

For transparency (at some expense in portability) I use a routine
I call "callg," named after the VAX instruction of the same name.
(This is equivalent to Peter Desnoyers' "va_call" routine; in
retrospect, I like his name better.)

va_call can be implemented in one line of assembly language on
the VAX; it typically requires ten or twenty lines on other
machines, to copy the arguments from the vector to the real stack
(or wherever arguments are really passed).  I have implementations
for the PDP11, NS32000, 68000, and 80x86.  (This is a machine
specific problem, not an operating system specific problem.)  A
routine such as va_call MUST be written in assembly language; it
is one of the handful of functions I know of that cannot possibly
be written in C.

Not all machines use a stack; some use register passing or other
conventions.  For maximum portability, then, the interface to a
routine like va_call should allow the type of each argument to be
explicitly specified, as well as hiding the details of the
argument vector construction.  I have been contemplating an
interface similar to that illustrated by the following example:

	#include "varargs2.h"

	extern printf();

	main()
	{
	va_stack(stack, 10);	/* declare vector which holds up to 10 args */

	va_push(stack, "%d %f %s\n", char *);
	va_push(stack, 12, int);
	va_push(stack, 3.14, double);
	va_push(stack, "Hello, world!", char *);

	va_call(printf, stack);
	}

Note that this calls the standard printf; printf need take no
special precautions, and indeed cannot necessarily tell that it
has not been called normally.  (This is what I meant by
"transparency.")

On a "conventional," stack-based machine, va_stack would declare
an array of 10 ints (assuming that int is the machine's natural
word size) and va_push would copy words to it using pointer
manipulations analogous to those used by the va_arg macro in the
current varargs and stdarg implementations.  (Note that "declare
vector which holds up to 10 args" is therefore misleading; the vector
holds up to 10 words, and it is up to the programmer to leave
enough slop for multi-word types such as long and double.  The
distinction between a "word" and an "argument" is the one that
always comes up when someone suggests supplying a va_nargs()
macro; let's not start that discussion again.)

For a register-passing machine, the choice of registers may
depend on the types of the arguments.  For this reason, the
interface must allow the type information to be retained in the
argument vector for inspection by the va_call routine.  This
would be easier to be implement if manifest constants were used,
instead of C type names:

	va_push(stack, 12, VA_INT);
	va_push(stack, 3.14, VA_DOUBLE);
	va_push(stack, "Hello, world!", VA_POINTER);

Since it would be tricky to "switch" on these constants inside
the va_push macro to decide how many words of the vector to set
aside, separate push macros might be preferable:

	va_push_int(stack, 12);
	va_push_double(stack, 3.14);
	va_push_pointer(stack, "Hello, world!");

(This option has the additional advantage over the single va_push
in that it does not require that the second macro argument be of
variable type.)  There is still a major difficulty here, however,
in that one cannot assume the existence of a single kind of
pointer.

For the "worst" machines, the full generality of C type names (as
in the first example) would probably be required.  Unfortunately,
to do everything with type names you might want to do, you have
to handle them specially in the compiler.  (On the other hand,
the machines that would have trouble with va_push are probably
the same ones that already have to have the varargs or stdarg
mechanisms recognized by the compiler.)

Lest you think that va_call, if implementable, solves the whole
problem, don't let your breath out yet: what should the return
value be?  In the most general case, the routines being indirectly
called might return different types.  The return value of va_call
would not, so to speak, be representable in closed form.

This last wrinkle (variable return type on top of variable
arguments passed) is at the heart of a C interpreter that allows
intermixing of interpreted and compiled code.  I know how I
solved it; I'd be curious to know how Saber C solves it.  (I
solved it with two more assembly language routines, also
unimplementable in C.  A better solution, to half of the problem,
anyway, would be to to provide a third argument, a union pointer
of some kind, to va_call for storing the return value.)

I just whipped together an implementation of the first example,
which I have appended for your edification and amusement, as long
as you have a VAX.

                                            Steve Summit
                                            scs at adam.pika.mit.edu

#!/bin/sh
sed 's/^X//' > pf.c <<\EOF
X#include "varargs2.h"
X
Xextern printf();
X
Xmain()
X{
Xva_stack(stack, 10);	/* declare vector which holds up to 10 args */
X
Xva_stack_init(stack);
X
Xva_push(stack, "%d %f %s\n", char *);
Xva_push(stack, 12, int);
Xva_push(stack, 3.14, double);
Xva_push(stack, "Hello, world!", char *);
X
Xva_call(printf, stack);
X}
EOF
sed 's/^X//' > varargs2.h <<\EOF
X#ifndef VARARGS2_H
X#define VARARGS2_H
X
X#define va_stack(name, max) int name[max+1]
X#define va_stack_init(stack) stack[0] = 0
X#define va_push(stack, arg, type) *(type *)(&stack[stack[0]+1]) = (arg), \
X		stack[0] += (sizeof(type) + sizeof(int) - 1) / sizeof(int)
X
X#ifdef __STDC__
Xextern int va_call(int (*)(), int *);
X#endif
X
X#endif
EOF
sed 's/^X//' > va_call.s <<\EOF
X.text
X
X.globl _va_call
X
X_va_call:
X	.word 0
X	callg *8(ap), *4(ap)
X	ret
EOF



More information about the Comp.lang.c mailing list