[comp.lang.c] Stacks and Parameters

firth@sei.cmu.edu (Robert Firth) (10/05/87)

The discussion on stacks and parameter pushing seems to me
to have confused several issues.  This post is intended to
be an attempt to separate them.

One starting point is the familiar parameter-passing sequence,
found for instance on the Vax-11:

	p(a1,a2,...an) =>

	push an
	push an-1
	...
	push a1
	build call frame
	invoke p

Within p(f1,f2...fn) we access the formals in order at increasing offsets
from the call frame.

This example shows several things

(a) The direction in which the stack grows is irrelevant.  The 'push'
    might be an autodecrement or an autoincrement.  The key fact is
    not the stack direction, but the fact that the call frame is built
    AFTER the parameters have been pushed.  If the lower-numbered formals
    are closer to the call frame, then evidently the parameter list must
    be built in reverse order.

(b) Even though the actuals are pushed in reverse order, they can be
    evaluated in any order.  If the actual is simple (as is usually
    the case), this is trivial.  If an actual is complex, one has to
    evaluate it into a register and subsequently push the register.

(c) Alternatively, one can always move the stack pointer N words and then
    evaluate the parameters left-to-right, filling the holes in the stack.

Why would one WANT the parameter list this way round?  If the number of
actuals is known at compile time (well, to be picky, if their SIZE is
known) then the order of the list is irrelevant.  In the above example,
we address fi at (frame pointer)+(i*4) or whatever; but we could
equally well push the parameters in direct order and address fi at
(frame pointer)+(n-i+1)*4.  The underlying reason for having f1 at a
fixed position relative to the call frame, is to permit P to take a
dynamically variable number of actuals.

Even then, there are alternatives.  For example, one could always pad the
actual parameter list to some fixed maximum (Ada permits this technique).
Not very efficient, but perhaps reasonable given that variadic procedures
are quite rare.

In addition, there is a different stack building technique. called an "open
stack" that is efficient for variadic procedures and allows the parameter
list to be built left to right.  It works like this:

	build call frame
	load a1
	load a2
	...
	load an
	invoke p

In other words, the actuals appear AFTER the call frame.  Clearly, this
has f1 at a fixed offset relative to the call frame, and also allows the
caller to pass as many actuals as it wants.  Incidentally, it is very
easy to add to this design the optimisation of passing the first few
actuals in registers - the caller leaves a hole in the stack; the called
procedure stores the registers there if necessary (ie if there is an inner
call or if the address of a formal is taken).

Finally, just to remind you that a "stack" is a programming technique
for the allocation of storage with LIFO extent semantics; it is not
necessarily a hardware feature.  Even if the engineers gave you an
autodecrement address mode, it still might be expedient to build the
software stack yourself, somewhere else.