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.
  1. The caller shall initialize the following registers prior to transferring control to the callee:

    1. GR1 shall contain the address of parameter list (i.e. a vector of parameter addresses).
      1. 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).
      2. 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.
      3. if no parameters are being passed then GR1 should be set to 0
    2. GR13 shall contain the address of an 18-word register save area
    3. GR14 shall contain the address of the instruction in the caller that the callee is to return to
    4. GR15 shall contain the address of the first instruction of the callee

    The contents of all of the other general registers is unspecified.

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

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

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

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

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

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

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

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

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

  2. Practically nobody ever bothered to follow the suggestion in clause 1.a.iii.

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

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

  5. A callee which didn't intend to ever act as a caller might not bother to setup its own register save area.

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

  7. No mechanism for returning anything other than integer or floating point values as function return values was specified.

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

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

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