Serial Reuse and Reentrancy
Usually when we write a program, we intend to execute it once, or once on each of a number of discrete occasions. We normally expect that when we start a job step, the load module will be loaded into memory from the disk, and then we execute it (perhaps several times in a loop), and after the job step, it is removed from memory. Next time we want to execute it, all this will happen again. The copy on the disk will be the same as before.
On the other hand, some assembly-language programs are intended to stay in memory longer than that. We may be writing something that supplements the operating system, such as a utility or an exit routine or even our own private access method. A number of different users might call the subprogram, either one after another or several at the same time. We may have to worry about the effect executing the program may have on its code. For instance, programs can change the values of their variables, and for that matter, a program can change its own executable code, although this is considered an extremely bad practice. It may be important to us that each time we use a load module, it should be in the same condition as the first time. How can we achieve this?
A program is referred to as serially reusable if a number of users can execute it--the same copy, already sitting in memory--one after another, without their actions interfering with each other.
(Specifically, if the program has local variables, a user might change their values, and this might leave the program in a changed state for the next user. The idea is to avoid this.)
For this reason, if a program is to be serially reusable, we need to ensure that each time it is executed, the local variables will have the same initial values. The normal way to achieve this is to do all initializations with executable code. Rather than declare a variable like this:
TOTAL DC PL3'0'
we would declare it with a DS statement:
TOTAL DS PL3
and then later set its initial value:
ZAP TOTAL(3),=P'0'
By default, the linkage editor marks all load modules as not reusable. If we want to have a reusable module, we need to (a) write code as described above, and (b) specify the REUS option to the linkage editor either in the PARM (as in PARM=REUS) or using the SETOPT control statement (as part of SYSLIN).
Why do we care? Suppose a subprogram’s load module is not marked reusable, and we call it dynamically. When it terminates, the operating system will mark it as no longer useable, and if we call it a second time, a second copy will be loaded. This is a waste of memory and time.
Another possibility is that we want to use static variables, that is, variables that do retain their values from on call to the next. (An example might be a page counter; we don't want every page to be numbered 1.) In this case, we may use the REUS option and mark a module as reusable even if it does not fit the definition of serial reusability. The linkage editor will take our word for this; it does not actually check the code to see if the program is reusable. (Notice that this is an issue only for a subprogram called dynamically.)
An analogy here might be to a situation in which several people are sharing the use of a kitchen, and each of them needs to use the large mixing bowl. Only one person can use it at a time. A previous user may have left the bowl dirty, that is, not in its original condition, and each person should clean the bowl before using it (i.e., reinitializing it).
A more extreme possibility is that we may want a load module to be reentrant. Here the idea is that there is one copy of the load module in memory and several users may be executing it at the same time, each using a different set of registers. That is to say, the only differences between what happens for one user and what happens for the next depend on the values in the registers. Reentrancy is a stronger condition than serial reusability.
In general, a reentrant program may not make any modifications to itself at all; that is, it cannot store any data into itself. We usually have registers containing addresses of variables somewhere else (obtained from a parameter list), and we can use those addresses to modify data elsewhere. We can also change the values of the registers.
A reentrant program can use literals and can have local storage of its own, but the values stored must be regarded as constants. All of the reentrant program's module can be thought of as "read-only".
Continuing the earlier analogy: the kitchen might contain a public bulletin board on which is posted a recipe for all the users to follow. No one is allowed to make changes to the recipe, and each user has his/her own set of ingredient and utensils.
What if a reentrant program needs some storage, such as the save area for its standard linkage? It can obtain storage dynamically using macros such as GETMAIN or STORAGE. The address of the new memory will be provided in a register. The program can modify that memory if necessary, as it is not part of the reentrant program's load module. Later the program should delete it using FREEMAIN or STORAGE.
(In the analogy, this corresponds to borrowing and returning utensils.)
If we want to use the standard IBM macros in a reentrant program, we will often need to use the list and execute formats of those macros. As we may not change the original copy of the parameter list, we need to carry out a sequence of steps:
1. Call the macro in list format using MF=L in the storage part of
the program. When this is assembled, a parameter list (some
amount of storage) will be declared and at least partially
initialized.
2. Obtain some memory dynamically.
3. Copy the parameter list into the dynamically-obtained memory.
4. Call the macro in execute format using MF=(E,address) where the
address refers to the copy we just made of the parameter list.
Example of the list and execute formats (reentrant case)
Suppose we are writing reentrant code and we may want to use the TIME macro to obtain the time of day repeatedly, sometimes asking for Greenwich Mean Time (ZONE=GMT) instead of local time (ZONE=LT). Our code to do so once might look something like this:
LA 3,TIMEMAC Reg. 3 = address of list form.
LA 4,TIMEEND Reg. 4 = address of the end of
* the parameter list.
SR 4,3 Now reg. 4 = length of the
* parameter list.
GETMAIN R,LV=(4) Obtain exactly enough memory.
LR 5,1 Reg. 5 = address of the new
* memory.
BCTR 4,0 Now reg. 4 = length – 1.
EX 5,THEMVC Copy the parameter list into
* the new memory.
TIME ZONE=GMT,MF=(E,1)) Call the macro in execute form.
where we have, in the storage part of the program,
TIMEMAC TIME DEC,ZONE=LT,MF=L
TIMEEND DS 0H
THEMVC MVC 0(0,4),0(3)
In this example, we are making just one change when we execute the macro. We could make several changes or none. When the macro is executed, a return code value may be stored in the parameter list,
which means we could be changing it. As reentrant programming does not allow us to modify our original module, we have to work with a dynamically-created copy of the parameter list.
It is interesting here that we have local storage (the parameter list) which we never touch, other than to make copies of it in dynamically-acquired storage.
(End of example)
As with “serially reusable", we will have to notify the linkage editor that “reentrant” is what we want. We will need to (a) write code as described above, and (b) specify the RENT option to the linkage editor either in the PARM (as in PARM=RENT) or using the SETOPT control statement (as part of SYSLIN). Also available is the assembler instruction RSECT (as an alternative to CSECT or START).
Who uses reentrant code? This is commonly used for operating system services. The modules in question are loaded into memory which is in common between the address spaces for various processes. (That is, address spaces all overlap to some extent or at least appear to overlap.) Likewise, the standard IBM macros are mostly--perhaps always--written with reentrant code.
Example of Reentrant Entry Linkage
This example uses the STORAGE macro's OBTAIN option to allocate dynamically 128 bytes, of which 72 are for the standard 18-fullword save area and the rest are for other uses. The address of the newly-allocated storage is available in register 1. Here $LOCAL is a DSECT describing the 128 bytes (starting with the save area). Don't forget to DROP it later.
EXAMPLE CSECT Beginning of the program.
* Entry linkage dynamically obtaining storage (128 bytes).
STM 14,12,12(13) Store the registers.
LR 12,15 Establish 12 as the base register.
USING EXAMPLE,12
LR 11,1 Save the address of the parameter
* list in register 11.
STORAGE OBTAIN,LENGTH=128 Get a new save/work area, etc.
ST 13,4(1) Cross-link the save areas.
ST 1,8(13)
LR 13,1 Register 13 = address of the
* newly-allocated storage.
USING $LOCAL,13 Attach $LOCAL to register 13.
LM 2,4,0(11) Unload the parameter list.
Notice that we need to save the address of the parameter list somewhere rather than leave it in register 1, which will be changed presently by the STORAGE macro. As this is a reentrant program, we will not have local storage in which to save it, so we use a register.
(End ofexample)
Example of Reentrant Exit Linkage
This example uses the STORAGE macro's RELEASE option to give back the 128 bytes we allocated earlier. This example does not provide a
return code in register 15, but it would be easy enough to do this.
* Exit linkage dynamically freeing storage (128 bytes).
LR 1,13 Set register 1 = address of
* the dynamically-allocated storage.
L 13,4(13) Set register 13 = address of the
* caller's save area.
STORAGE RELEASE,ADDR=(1),LENGTH=128
* Free the storage.
LM 14,12,12(13) Restore the registers.
BR 14 Return to the calling routine.
* End of the program’s executable code.
(End of example)