[comp.lang.c] Needed: A

kemnitz@mitisft.Convergent.COM (Gregory Kemnitz) (06/02/89)

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.  (I suppose
I could use the maximum number of arguments that any function has, but is
this portable??)  I know how many arguments there are when I call the function,
and what those arguments are.  Is there any alternative (like varargs) to
a big switch block or calling with the max number of arguments??
-- 
----------------------------------+--------------------------------------
Greg Kemnitz                      |  "He who does not understand baseball
kemnitz@Convergent.COM            |   will never understand America"
                                  |  --Tocqueville

gwyn@smoke.BRL.MIL (Doug Gwyn) (06/02/89)

In article <708@mitisft.Convergent.COM> kemnitz@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.

Let's assume for the sake of simplicity that all the arguments have the
same type.  Then your best bet is to define all the functions to take
a pointer to (an array of) arguments.  That way they're all conformable.

desnoyer@Apple.COM (Peter Desnoyers) (06/03/89)

In article <708@mitisft.Convergent.COM> kemnitz@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.  (I suppose
>I could use the maximum number of arguments that any function has, but is
>this portable??)  I know how many arguments there are when I call the function,
>and what those arguments are.  Is there any alternative (like varargs) to
>a big switch block or calling with the max number of arguments??
>-- 
>----------------------------------+--------------------------------------
>Greg Kemnitz                      |  "He who does not understand baseball
>kemnitz@Convergent.COM            |   will never understand America"
>                                  |  --Tocqueville

I have run into this problem in writing event-driven simulations, and
have solved it two times in two different ways -

 (a) - use the maximum number of arguments. This won't break on any
compiler I know of. However, if you want to be strictly conforming,
if f is a function which you are using a pointer to, you should change 

  f( a, b, c)
  f( a, b, c, ...) /* may be slightly incorrect */ 

so that it will be guaranteed to be able to digest the surplus
arguments. 

 (b) write a non-portable function va_call( f, n, args[]) that calls f
with the N arguments in 'args'. You could write it in assembler or use
inline assembler to access the stack and frame pointers.

				Peter Desnoyers

scs@adam.pika.mit.edu (Steve Summit) (06/04/89)

In article <708@mitisft.Convergent.COM> kemnitz@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@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

carroll@s.cs.uiuc.edu (06/05/89)

/* Written 11:58 am  Jun  2, 1989 by gwyn@smoke.BRL.MIL in s.cs.uiuc.edu:comp.lang.c */
In article <708@mitisft.Convergent.COM> kemnitz@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. (...) 
Let's assume for the sake of simplicity that all the arguments have the
same type. (...)
/* End of text from s.cs.uiuc.edu:comp.lang.c */
If that's not the case, you can wrap the arguments in a union, and pass
an array of those, e.g. (assuming you can count on argument types)
union Argument {
        int i;
        float j;
        char *s;
        } ;
union Argument arg_list[MAX_ARGS];  /* array to pass */

Alan M. Carroll                "And there you are
carroll@s.cs.uiuc.edu           Saying 'We have the Moon, so now the Stars...'"
CS Grad / U of Ill @ Urbana    ...{ucbvax,pur-ee,convex}!s.cs.uiuc.edu!carroll

blarson@skat.usc.edu (Bob Larson) (06/05/89)

In article <11830@bloom-beacon.MIT.EDU> scs@adam.pika.mit.edu (Steve Summit) writes:
>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.)

??? Then how come my os9/68k compiler for the 68020 uses a different
calling convention than the sun 3 compiler?  This problem is most
definitly dependant on the calling convention your compiler happens
to be using at the moment, which can vary with cpu, operating system,
and even compiler options.

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

This almost sounds like a chalange to find a machine where it can be
done in non-portable C.  I'm sure there is one out there somewhere.

-- 
Bob Larson	Arpa:	blarson@skat.usc.edu
Uucp: {uunet,cit-vax}!usc!skat!blarson
Prime mailing list:	info-prime-request%ais1@ecla.usc.edu
			usc!ais1!info-prime-request

desnoyer@Apple.COM (Peter Desnoyers) (06/06/89)

In article <11830@bloom-beacon.MIT.EDU> scs@adam.pika.mit.edu (Steve Summit) writes:
> [routine called va_call that takes a function pointer, an argument
> count, and an array of arguments]
>
>va_call can be implemented in one line of assembly language...
>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.

Almost correct. In Turbo C there are pseudo-variables for the stack
pointer, etc.  You can also access these registers (warning -
approaching gross kludge) by doing a setjmp and knowing the jmp_buf
format. 

				Peter Desnoyers

gis@datlog.co.uk ( Ian Stewartson ) (06/10/89)

In article <11830@bloom-beacon.MIT.EDU> scs@adam.pika.mit.edu (Steve Summit) writes:
>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.

It seemed to me that the printf and scanf (and in particular the vprintf
family) have a similar problem looked at in a mirror.  They effectively do
what you want in reverse.  I noticed that all the printf family use the
function _doprnt (possibly not on all systems, but certainly the few that
I have access to).  Therefore va_call could be implemented in the similar
manner.  Taking Steve's example code, I hacked it to produce a macro
va_access to get the top of the argument stack excluding the count and then
modified pf.c to produce the following code:

#include <stdio.h>
#include "varargs2.h"

#define va_access(stack)	&stack[1]

main()
{
    va_stack(stack, 10);

    va_stack_init(stack);

    va_push(stack, 12, int);
    va_push(stack, 3.14, double);
    va_push(stack, "Hello, world!", char *);

    _doprnt ("%d %f %s\n", va_access(stack), stdout);
    printf ("%d %f %s\n", 12, 3.14, "Hello, world!");
}

Which generates the strings:

12 3.140000 Hello, world!
12 3.140000 Hello, world!

(The last printf to check the results).

The declaration of _doprnt is of the form

    int		_doprnt (format, args, iop)
    char	*format;
    va_list	args;
    FILE	*iop;

and the arguments are accessed within _doprnt using

    width = va_arg(args, int);

There being no va_start and va_end in the function.

Now I realised that _doprnt is a special case on Unix systems and may not
exist on other systems MS-DOS (which uses _output in MSC5 with iop as the
first parameter) or MPE (HP's proprietary 3000 system) which has an odd
stack frame format which includes a count of the number of arguments and
flags to indicate if an argument is on the stack.  However, from what I
remember of the version of C we were using on MPE, the printf family
shared common code and varargs definitely existed.  So it may be possible
to generate portable code, especially if varargs exists on the intended
system.

I tested the code on our Vax (Sys Vr2) and MS-DOS systems.

On a personnel note:  Thanks Steve, your include file and usage saved me
time I would have wasted using a different method.

Regards,

Ian Stewartson
Data Logic Ltd, Queens House, Greenhill Way, Harrow, Middlesex, HA1 1YR, UK.
(Phone) +44 1 863 0383 (Telex) 888103 (Fax) +44 1 861 2010
(Network) gis@datlog.co.uk or ukc!datlog!gis