The primary
linkage convention used on
IBM's
System/360 (S/360) and
System/370 (S/370)
operating systems was called the
S-type
linkage convention.
The
S was a reference to the use of
storage (i.e.
memory) to pass
parameters to the routine being called.
There was another linkage convention called the
R-type linkage convention which was identical to the
S-type linkage convention except that
parameters were passed in
registers.
For simplicity, I'm going to refer to the hardware architecture
as S/370 even though we're really talking about both the S/360
and S/370 architectures here.
Overview of the S-type linkage convention
Unlike most (all?) modern microprocessors,
programs running on an S/370 didn't have a function call stack.
Instead, when a subroutine was being called, the caller was
responsible for providing a register save area which the
callee could use to save the caller's general purpose registers.The callee would then setup their own register save area
so that they could act as a caller if necessary (routines
which knew that they would never call anything might not bother
to setup their own register save area).
The caller would pass a vector of parameter addresses called
a parameter list to the callee.
The address of the first element of this vector would be passed in GR1 (i.e.general register 1).
When the callee was ready to return, it would restore the
registers which it had saved earlier, set what was called a return code
indicating whether or not the call had succeeded,
optionally specify a return value (i.e. function call result)
and then return to the caller.
The handful of Unix implementations for the S/370 architecture may have defined a linkage convention that used a function call stack.
I just don't know.
The S-type linkage convention
This section is a very formal description of the S-type linkage convention.
A commentary and an annotated example follows.
The reader may find it useful to read the overview of the S/370 architecture located here before proceeding.
- The caller shall initialize the following registers
prior to transferring control to the callee:
- GR1 shall contain the address of parameter list
(i.e. a vector of parameter addresses).
- each word in the parameter list shall
contain the address of a parameter (i.e. the elements of the
vector contain the addresses of parameters, not the parameter
values themselves).
- if the callee expects a variable number of parameters
to be passed then the caller shall append a word with a value of 0 after the last parameter address in the parameter list.
- if no parameters are being passed then GR1 should be set to 0
GR13 shall contain the address of an 18-word register save area
GR14 shall contain the address of the instruction in the caller
that the callee is to return to
GR15 shall contain the address of the first instruction of the callee
The contents of all of the other general registers is unspecified.
The callee shall have its own register save area, the address of
which shall be placed into GR13 before the callee's body's
code starts executing.
The callee shall save the address of the caller's
register save area in the
second word of the callee's register save area
(i.e. the backward save area pointer indicated in the diagram below).
The callee shall save the address of the callee's register
save area into the third word of the caller's register save
area (i.e. the forward save area pointer indicated in the diagram
below).
The caller shall be responsible for preserving before the call the contents
of any floating point registers which it may need after the callee
has returned.
The callee shall preserve the values of GR0 through GR12, GR14 and GR15, as they existed when the callee was entered,
into the caller's register save area in the locations
specified by the diagram below.
The callee shall indicate the success or failure of the call by
placing an appropriate return code into GR15 prior to returning to the caller.
A return code of 0 shall mean that the call succeeded.
Successive multiples of 4 shall be used to indicate failure types which
are deemed worthy of being distinguished from each other.
The callee shall not return a return code which
isn't documented as being a possible return code value for the
callee routine.
If the callee wishes to return an integer value (i.e. act as
a function returning an integer value) then it shall leave the return
value in GR0 when it returns to the caller.
If the callee wishes to return a floating point value (i.e. act as
a function returning a floating point value) then it shall leave the return
value in FPR0 (floating point register 0) when it returns to the caller. It is the responsibility of the caller to know what size of floating
point value is being returned by the callee.
Format of a register save area:
+----------------------------+
| unused |
+----------------------------+
| backward save area pointer |
+----------------------------+
| forward save area pointer |
+----------------------------+
| GR14 |
+----------------------------+
| GR15 |
+----------------------------+
| GR0 |
+----------------------------+
| GR1 |
+----------------------------+
| GR2 |
+----------------------------+
| GR3 |
+----------------------------+
| GR4 |
+----------------------------+
| GR5 |
+----------------------------+
| GR6 |
+----------------------------+
| GR7 |
+----------------------------+
| GR8 |
+----------------------------+
| GR9 |
+----------------------------+
| GR10 |
+----------------------------+
| GR11 |
+----------------------------+
| GR12 |
+----------------------------+
Commentary
- Clause 1.a.ii suggests that the trailing 0 at the end of the
parameter list was only required if the callee expected
a variable number of parameters.
This could be incorrect - the trailing 0 might have been mandatory.
In practical terms, the trailing 0 was optional except if the callee
expected a variable number of parameters as a failure to provide it would
go undetected.
- Practically nobody ever bothered to follow the suggestion in clause 1.a.iii.
- Clause 4. states a mandatory requirement that the callee's register
save area address be stored into the third word of the caller's
register save area.
This was particularily useful when diagnosing a dump (the S/370
equivalent of a Unix core file) as it made it possible to start
with the main routine's register save area and trace downwards
to the routine that failed.
Unfortunately, this clause was widely ignored by assembly language
programmers and compiler writers.
Since the person looking at the dump could not rely on the rule being followed, the rule was essentially useless.
- Clause 6. states a mandatory requirement that the callee
save GR1 through GR12, GR14 and GR15 into the caller's save area.
In practice, routines might avoid saving registers that they didn't
intend to modify in order to reduce the call's overhead.
- A callee which didn't intend to ever act as a caller
might not bother to setup its own register save area.
- The last sentence of clause 7 which prohibits the callee from
returning unexpected return code values allowed the caller
to use a branch table (i.e. a switch statement) to handle different
return code values.
The wise programmer implementing a caller always assumed
that the callee might return return code values higher
than documented.
Nobody ever bothered to check if the return code value was a multiple of
four (i.e. rather spectacular program failures could and often did result if
a return code value which wasn't a multiple of 4 was returned).
- No mechanism for returning anything other than integer or floating
point values as function return values was specified.
- The diagram indicates that the first word of a register save area
was unused.
I have a recollection that this word had a defined purpose.
I'm also certain that practically nobody ever bothered to use it for the
purpose for which it was defined.
- When used correctly, the S-type linkage convention provided
a very complete mechanism for implementing the mechanics of a function call.
Although it wasn't unusual for programmers to take shortcuts, the
convention worked well and was widely used.
The S/370 FORTRAN implementations used it which, in turn, ensured that
any library which was to be used by FORTRAN programmers had to use it (i.e. it was VERY widely used).
- The register save area could be statically defined
or dynamically allocated.
If a routine was to be called recursively or needed to be re-entrant
then the register save area had to be allocated dynamically.
Register save area chaining
Assume that routine MAIN has called routine HELLO
which has called routine WORLD.
If clauses 3 and 4 have been properly adhered to by all three
routines, here's how the four register save areas would be
chained together once the body of WORLD had been entered:
+-------------------+
| |
| |
V |
+-----+ +---- | -----------+
| ~~~ | | | |
+-----+ | | |
| 0 | V | |
+-----+ +-----+ | +---- | -----------+
| ---------->| ~~~ | | | | |
+-----+ +-----+ | | | |
| | | ------+ V | |
| | +-----+ +-----+ | |
| . | | --------->| ~~~ | | |
| . | +-----+ +-----+ | |
| . | | | | ------+ |
| | | | +-----+ +-----+ |
| | | . | | --------->| ~~~ | |
+-----+ | . | +-----+ +-----+ |
system | . | | | | ------+
provided | | | | +-----+
register | | | . | | ~~~ |
save area +-----+ | . | +-----+
MAIN's | . | | |
register | | | |
save area | | | . |
+-----+ | . |
HELLO's | . |
register | |
save area | |
+-----+
WORLD's
register
save area
A few points are in order:
- ~~~ is used to indicate locations with undefined values.
- The system provided register save area is the save area provided by the operating system system when MAIN was called.
- the register contents as they existed when MAIN was entered
are currently preserved (i.e. saved) in the system provided register save area.
- the register contents as they existed when HELLO was entered
are currently preserved in MAIN's register save area.
- the register contents as they existed when WORLD was entered
are currently preserved in HELLO's register save area.
- as WORLD has yet to call anything, the forward save area
pointer in WORLD's register save area is undefined.
Entry, call and exit sequences
This example assumes:
- that you, the reader, are familiar with the S/370 instruction set
and are familiar with the concept of a base register and what a
USING statement does.
A future writeup will (hopefully) fill these gaps.
- that the routine has a statically defined register save area
(dynamically allocated register save area entry sequences are hopelessly
OS-dependent).
- that your browser's screen is wide enough to avoid wrapping the lines
in the example.
This example is actually a complete S/370 assembler language function which takes two integer parameters and returns the product of their
values.
In practice, the S/370 assembler's macro language would have been used
to implement the entire entry sequence and the entire exit sequence
in a couple of lines or so each.
We start by naming the routine and then
defining symbolic names for the 16 general purpose registers.
FRED CSECT
GR0 EQU 0
GR1 EQU 1
GR2 EQU 2
GR3 EQU 3
GR4 EQU 4
GR5 EQU 5
GR6 EQU 6
GR7 EQU 7
GR8 EQU 8
GR9 EQU 9
GR10 EQU 10
GR11 EQU 11
GR12 EQU 12
GR13 EQU 13
GR14 EQU 14
GR15 EQU 15
The entry sequence saves the caller's register into their save
area, sets up the forward and backward save area links and sets GR13
to this routine's save area:
STM GR14,GR12,12(GR13) Save GR14, GR15 and GR0 through GR12
* in caller's register save area.
LR GR12,GR15 Copy the address of FRED into
* GR12 for use as a base register.
USING FRED,GR12 Notify the assembler that GR12
* contains the address of FRED.
LA GR15,SAVEAREA Get the address of our save
* area into a register.
ST GR13,4(GR15) Store the address of our caller's
* save area into the second word of
* our save area.
ST GR15,8(GR13) Save the address of our save area
* int the third word of our caller's
* save area.
LR GR13,GR15 Set GR13 to point to our save area.
My former students that I taught IBM assembler language and operating
system concepts to back in the early 1980s
won't forgive me if I don't add the following
annotation (everyone else can just ignore this):
A USING statement is a promise to the assembler that a
particular register contains the address of a
particular memory location.
At this point, we're ready to bring in the parameters (via the parameter list
specified in GR1).
Note that we can't call another routine until we've either loaded the parameter addresses from the parameter list or at least preserved GR1 as calling another routine will destroy the current contents of GR1 (we could always reach back into the caller's register save area to recover the contents of GR1 but that's a hassle which is best avoided).
There's no particular reason why we use GR2, GR3 and GR4 here (we could have used
any register between GR2 and GR11 inclusive).
There's also no particular reason to not reuse GR2 when we're loading the second parameter's address and value other than that using two different registers makes it slightly
easier to debug the program at the machine code level.
L GR2,0(GR1) Load the address of the first
* parameter into GR2.
L GR4,0(GR2) Load the value of the first
* parameter into GR4.
L GR3,4(GR1) Load the address of the second
* parameter into GR3.
M GR4,0(GR3) Multiply GR4 by the second parameter,
* leaving the result in GR4.
Pass the product to a routine called MAGIC (to demonstrate how to call a routine):
ST GR4,PRODUCT store the product into memory
LA GR1,MAGIC_PARMS Load address of magic's
* parameter list.
L GR15,=V(MAGIC) Load the address of the
* external symbol MAGIC
* into GR15.
BALR GR14,GR15 Branch to the address
* in GR15 while storing
the link (i.e. return)
address in GR14.
We're back from MAGIC. Time to return to our caller but first we load the product into GR0 from where we left it in GR4.
LR GR0,GR4
The exit sequence restores all of the general registers to what they were when FRED was entered with the exception of GR15 (the return code) and GR0 (the return value).
It then returns control to the caller:
L GR13,4(GR13) Get the address of our caller's
* save area into GR13.
LM GR14,GR15,12(GR13) Restore GR14 and GR15 from the caller's.
* save area.
LM GR1,GR12,24(GR13) Restore GR1 through GR12 from the
* caller's save area.
* Note that we don't restore GR0 as it
* now contains our return value.
SR GR15,GR15 Set GR15 to 0 (i.e. subtract it from itself).
BR GR14 Return to the caller.
The definition of the space for FRED's save area would have then followed:
SAVEAREA DS 18A Set aside 18 words (i.e. address-sized
memory locations).
The definition of space for the PRODUCT and the parameter
list that we pass to MAGIC.
PRODUCT DS F fullword integer (i.e. 4 bytes)
MAGIC_PARMS DC A(PRODUCT,0) Parameter list consisting
* of the address of PRODUCT
* followed by a 0.
And that's would have been that!
END
The System/370 assembler supported a very powerful macro language and each S/370-based operating system came with a library of macros.
The following is the above program written to take advantage of the macro library on one of the S/370 operating systems that I used quite a bit.
I've also used a LM (Load Multiple) instruction to get both parameter addresses in one step.
For added realism,
this version isn't annotated and comments are at about the
level of density that one would have found in a real S/370 assembler program.
FRED CSECT
GR REQU
ENTER GR12,SA=SAVEAREA entry sequence (with GR12
* as our base register).
*
* Compute the product of the two parameters
*
LM GR2,GR3,0(GR1)
L GR2,0(GR2)
M GR2,0(GR3)
*
* Pass the product to MAGIC
*
ST GR2,PRODUCT
CALL MAGIC,(PRODUCT)
*
* Done.
*
EXIT PRODUCT,RC=0
*
* Data area
*
SAVEAREA DS 18A
PRODUCT DS F
END
Sources
- Assembler Language Programming: the IBM System/360 and 370;
by George W. Struble; Copyright © 1975, 1969 by Addison-Wesley
Publishing Company, Inc.;
ISBN 0-201-07322-6
- Personal recollections.