On 12/15/23, Greg 'groggy' Lehey <grog(a)lemis.com> wrote:
At least in assembler (I never programmed HLLs under MVS), by
convention R13 pointed to the save area. From memory, subroutine
calls worked like:
LA 15,SUBR load address of subroutine
BALR 14,15 call subroutine, storing address in R14
The subroutine then starts
with
STM 14,12,12(13) save registers 14 to 12 (wraparound) in old save
area
LA 14,SAVE load address of our save area
ST 14,8(13) save in linkage of old save area
LR 13,14 and point to our save areas
Returning from the subroutine was then
L 13,4(13) restore old save area
LM 14,12,12(13) restore the other registers
BR 14 and return to the caller
Clearly this example isn't recursive, since it uses a static save
area. But with dynamic allocation it could be recursive.
Yes, that was the most common calling convention in S/360/370., and
the one that was used if you were implementing a subroutine package
for general use. It has the advantage that the (caller-allocated)
register save area has room for all of the registers and so there is
no need to change the caller code if the callee is changed to use an
additional register. It also makes it very convenient to implement
unwinding from an exception handler. But it does burn 60 bytes for
the register save area and if you're programming for a S/360 model 25
with only 32K of user-available memory that can be significant.
Those writing their own assembly code typically cut corners on this
convention in order to reduce the memory footprint and the execution
time spent saving/restoring registers.
There's been long debate by ABI and compiler designers over the
relative merits of assigning the duties of allocating the register
save area (RSA) and saving/restoring registers to either the caller
or the callee. The IBM convention has the caller allocate the RSA and
the callee save and restore the register contents. One can also have
a convention where the caller allocates an RSA and saves/restores the
registers it is actively using. Or a convention where the callee
allocates the RSA and saves/restores the registers it has modified.
Each convention has its merits and demerits.
The IBM PL/I compiler for OS and OS/VS (but not DOS and DOS/VS) had
three routine declaration attributes to assist in optimization of
routine calls. Absent any other information, the compiler must assume
the worst--that the subroutine call may modify any of the global
variables, and that it may be recursive. IBM PL/I had a RECURSIVE
attribute to flag routines that are recursive. It also had two
attributes--USES and SETS--to describe the global variables that are
either used by (USES) or changed by (SETS) the routine. Global
variables not in the USES list did not have to be spilled before the
call. Similarly, global variables not in the SETS list did not have
to be re-loaded after the call.
IBM dropped USES and SETS from the PL/I language with the S/370
compilers. USES and SETS were something of a maintenance nightmare
for application programmers. They were very error-prone. If you
didn't keep the USES and SETS declarations up-to-date when you
modified a routine you could introduce all manner of subtle stale data
bugs. On the compiler writers' side, data flow analysis wasn't yet
advanced enough to make good use of the USES and SETS information
anyway. Modern compilers perform interprocedural analysis when they
can and derive accurate global variable data blow information on their
own.
-Paul W.