M
Michael Wojcik
What systems use separate stacks for return addresses and
arguments? I am not aware of any in the modern world, so it
would be educational to hear about them.
The ever-lovin' AS/400 (now iSeries) OPM (Original Program Model)
appears to, based on the documentation for MI, the pseudo-assembly
language supplied with OS/400. (I say "appears to" because I'm
taking this from the somewhat-sparse documentation, my own
recollections, and what I've read from IBM insiders on Usenet; I'm no
expert on AS/400 internals.)
The first thing to remember about the AS/400, for programs running
in normal iOS mode (and not PACE or NT or Linux or whatever else
might be supported these days), is the "single-level store". Every
object in the machine has one address in a single 64-bit virtual
address space. (Pointers are actually 128 bits because they contain
a bunch of other information.) There's no per-process virtual
addressing.
Thus in a sense return addresses and parameters are in the "same"
area, but only because there's only one area. They probably won't
be contiguous, and it's not really a stack.
Security is provided by a combination of trusted compilation from
intermediate code into the native instruction stream, the
underlying capability architecture, and some hardware protections,
though the last play less of a role than in more typical process-
virtual-addressing systems.
MI (and the languages based on it such as OPM COBOL) supports three
call operations:
Call Internal (CALLI)
Call External (CALLX)
Call Program with Variable Length Argument List (CALLPGMV)
(There's also "program activation", which is sort of like dynamic
loading without invoking; and "transfer control", which is like
the exec system call in Unix; but I'll ignore those here.)
For an internal call, which creates a subactivation within the
current activation (the equivalent of a "normal call" on one of
those puny regular computers), the calling routine provides an
instruction pointer as the third operand, and the return address
is placed in that pointer. The called routine receives this
pointer and uses it as the operand of a Branch (B) instruction
to return. (Yes, an internal return is just a branch.)
Said instruction pointer must be allocated from (what passes for)
the heap or (what passes for) static data by the calling routine.
Paramerers, on the other hand, are specified an an operand list (OL)
pointer which is another operand of the CALLI operation. The called
routine should declare an operand list corresponding to the
parameters it expects to receive. When it's called, it can reference
those parameters, which will create a temporary mapping in the form
of space pointers to the parameter objects (all parameters are passed
by reference).
External calls are similar, except that a new activation is created
using the specified program object (which is sort of like a program
or shared library on less-interesting systems), and control is passed
to its program entry procedure (if it's a bound program) or external
entry point (if it's non-bound). It also takes an OL as an operand,
but rather than a return instruction pointer, the calling routine can
provide an optional "instruction definition list", which is basically
a list of addresses that the called program can return to. So the
called program doesn't have to return to the place from whence it was
called. Fun!
An external-called program returns using the External Return (RETX)
opcode. If the called program is non-bound and the caller supplied
a return list, the called program can provide an integer operand
that's an index into the return list to say which return point to
return to.
There are a bunch of other differences between external calls to
bound and non-bound programs, but I'll gloss over them.
Call Program with Variable Length Argument List is like CALLX except
that it takes an array of parameters and a count, and it omits the
list of possible return locations (the called program has to return
to the place it was called from).
Of course, these days not many people use OPM; they've mostly
switched to ILE, the Integrated Language Environment. ILE does use a
call stack and supports pass-by-value (and pass-by-reference and
pass-by- reference-to-temporary-copy), though it's still rather more
complicated than just the sort of move-and-decrement that's typical
of less-CISCy architectures.
These and many more intriguing details can be found at:
http://publib.boulder.ibm.com/infocenter/iseries/v5r3/index.jsp
(Requires Javascript and all manner of such things.) Look especially
at Programming -> APIs -> MI Programming and Programming -> Languages
-> ILE Concepts.
Such a system might be
more resistant to "stack smashing" buffer overflow attacks, for
one thing.
Well, the '400 is certainly more resistant than many other platforms
to stack smashing.