Systems Programming with the SAS/C Compiler |
The previous section explained how the SPE framework
is created and destroyed with start-up routines and L$UMAIN. This section
discusses other SPE framework routines that are required at execution time.
The first routines discussed are the routines that handle
the stack, L$UPROL and L$UEPIL. Next, the details of L$UPREP are covered.
Following this discussion is an explanation of L$UTFPE, the routine responsible
for handling math function exceptions. Then, L$UTZON, a routine that allows
you to define the offset between local time and Greenwich time, and L$UWARN,
the routine that handles conditions that call for a diagnostic message, are
covered. Finally, the routine L$UHALT, which is called by the library to force
abnormal termination, is described.
Implementation
of C under the IBM 370 architecture requires the use of a software stack for
storing
auto
variables, temporary results, and items of miscellaneous status,
such as saved registers. Each function must obtain stack space on entry and
free stack space on return. Stack management is provided by prolog and epilog
routines. The compiler generates instructions to call these routines on function
entry and return.
Because the prolog and epilog are used for every function
call, their performance has a significant impact on the performance of the
entire program. But there is a trade-off between the performance of the prolog
and epilog and their functionality. For convenient debugging, it can be helpful
to add instructions to the prolog and epilog to save additional status information,
even though performance is reduced.
While the exact behavior of the prolog and epilog can
be changed according to the needs of the framework, note that many components
of the SPE implementation are interdependent with the prolog and epilog. Notably,
L$UEXIT (
exit
), L$UJUMP (
longjmp
), L$UZSIR (USS signal handling), L$UEXLK
(
bldexit
), and L$UBTRC (
btrace
) are closely involved with the details of
stack management. Any change to the prolog and epilog has the possibility
of some effect on these routines.
The prolog entry point is L$UPROL, which is called when
a function that requires a DSA is entered. The epilog entry point is L$UEPIL,
which is called when a function that requires a DSA returns. Linkage to both
routines is indirect. The addresses of L$UPROL and L$UEPIL are stored in
the CRAB (in fields CRABPRLG and CRABEPLG) by L$UMAIN as part of framework
creation. The compiler generates code to load these addresses and branch
to them. (L$UPROL is also called by L$UPREP.)
In each C function, the compiler generates code to branch to
the prolog, using the following conventions:
The prolog returns to the function via
R5. To return,
the prolog branches to offset X'3E' (that is, the symbolic name CPROGO) from
the address in R5. Usually, when the prolog is invoked, R5 and R15 have the
same value, but this is not necessarily so for INDEP functions.
When the prolog returns to CPROGO, the code generated
by the compiler at this point expects the following to have been done:
Although not required by compiled code, the epilog requires
that the value of R15 on entry be saved. L$UPROL stores this value in the
DSAPRBSV field of the new DSA.
The compiler generates code to branch to the epilog, using the
following conventions:
To return to the function, the
epilog branches to offset
X'36' (symbolic name CPROEXIT) from the function's entry point. Note that
the epilog cannot obtain the function's entry point from the R15 slot of the
previous save area because the function may have stored a return value there.
R1 and registers 6 through 12 must be unchanged when the epilog returns.
Registers 2 through 5 and R13 are restored as necessary by compiler-generated
instructions in the function.
The SPE version of L$UPROL uses a single block of memory
allocated by L$UMAIN during framework creation as a stack and issues an abend
if it overflows. This is a common way to implement a software stack, representing
a compromise between maximum dependability and maximum performance.
L$UPROL performs the following steps. Certain operations
may be useful for debugging and are marked as optional. In the object code
for L$UPROL, optional steps have been disabled. L$UPROL
-
stores registers 6 through 11
(optional).
-
checks for stack overflow and abends if a new
DSA cannot be allocated.
-
updates the stack top pointer.
-
stores the address of the previous save area in
the new DSA.
-
stores the address of the new DSA in the previous
save area (optional).
-
saves R15 in DSAPRBSV for the epilog.
-
copies an eye-catcher and a flag byte into the
first word of the DSA. (Unless the program is in an INDEP framework, the eye-catcher
is useful only for debugging.)
-
links the new DSA to the previous DSA (which may
not be the same as the previous save area if there is an intervening non-C
routine), and saves the new DSA address in the CRAB.
-
copies the function name to the DSAOWNER field
(optional).
-
saves R1 in the DSAPARMS field.
-
loads R4 with the address of the constant CSECT
from the CPROCONS field.
-
returns to the function.
The L$UEPIL entry point in L$UPROL performs
the following steps:
-
updates the stack top and current DSA fields in
the CRAB
-
loads R14 with the address of the entry point
of the function from the DSAPRBSV field
-
restores registers 6 through 11 (optional)
-
returns to the returning function
at the CPROEXIT
offset.
L$UPREP is mentioned
in the preceding section as one of the INDEP framework support routines. Its
function is to determine if the C framework has been created. If it has been
created, then L$UPREP recovers the framework and returns to the calling function.
If not, then L$UPREP calls L$UMAIN to create the framework.
L$UPREP makes a distinction between two types of function
calls based on the register save area (addressed by R13) associated with the
caller. The caller may be a C function, in which case the save
area is part of a C DSA, or a non-C function, in which case the
save area is not a C DSA. As mentioned in the L$UPROL discussion, C DSAs
are distinguished by the "CSA" marker at offset 0 from R13. Note that functions
written in assembler using the CENTRY and CEXIT macros are indistinguishable
from functions written in the C language and are therefore considered to be
C functions. Non-C functions include routines written in another high-level
language, assembler routines that do not use CENTRY and CEXIT, and the operating
system components.
L$UPREP is used only in an INDEP framework. When the
indep
compiler option is used, the code generated by the compiler to call the prolog
is changed. The
indep
option causes the compiler to generate a branch to the L$UPREP
routine. A function compiled with the
indep
option takes this branch almost
immediately after entry.
L$UPREP takes one of the following three paths:
When L$UPREP is entered, it inspects the save area addressed
by R13 to determine whether the caller is a C function. If "CSA" appears
at offset 0 (the DSACSA field in the DSA), then L$UPREP assumes that the caller
is a C function.
If L$UPREP does not find "CSA", it invokes the L$UCENV
macro to determine if the framework has already been created. L$UCENV returns
the address of a location where a pointer to the CRAB should be (or has been)
stored. If the location contains 0, then the framework has not been created.
If it has been created, then L$UPREP loads the CRAB address into R12.
If the framework has been created, then, before returning
control to the function, L$UPREP checks to see if the function requires a
DSA. If it does, L$UPREP invokes L$UPROL to create a DSA.
When L$UPROL returns, L$UPREP marks the DSA as one belonging
to a function compiled with the
indep
option and sets the DSANJUMP flag in the DSAFLGT
field. This flag prohibits a
longjmp
over the function. This prohibition is established
for functions compiled with the
indep
option because the caller of such a function
may be written in another language, and most high-level languages do not expect
longjmp
type returns. If the caller can handle this sort of branching, the
DSANJUMP flag does not need to be set.
If the framework has not been created, L$UPREP calls
L$UMAIN to create it. L$UPREP loads R1 with the address returned by L$UCENV
so that L$UMAIN will store the CRAB pointer for later recovery. After the
framework has been created, L$UMAIN calls the function directly, placing its
own return address in R5. Upon entry, the function again calls L$UPREP immediately.
(Note that this is a recursive call.) Using the logic described above, L$UPREP
determines that the function was called from a C function.
L$UPREP also enforces a basic convention of the INDEP
framework: if the called function is the
main
function, the framework is destroyed
when
main
returns by calling L$UMAIN. However, in the general case, upon
return from the function, L$UPREP restores the registers (including R14) from
the save area of its caller's caller and returns to the address in R14. This
branch transfers control back to the routine that invoked the function that
created the INDEP framework. L$UMAIN's save area, anchored in CRABMDSA, is
left intact. This leaves the C framework in place for subsequent calls.
The SPE L$UPREP is identical to the standard C library
L$UPREP and can be used to replace the standard C library L$UPREP. Refer to Using the indep Option for Interlanguage Communication for more
information.
When the C framework is created, L$UMAIN
stores the CRAB address in some appropriate location. The L$UCENV macro defines
a CSECT also named L$UCENV for this purpose. When the macro is invoked by
L$UPREP, L$UCENV returns the address of the CSECT as the address of the CRAB
pointer. Because this implementation forces the program to be non-reentrant,
applications that need to be reentrant should use a different method of storing
the CRAB address.
Under CICS, the L$UCENV macro uses the first word of
the CICS transaction work area (TWA) to store the address of the CRAB pointer.
You may need to decide whether or not this technique is appropriate for your
application's environment. The storage allocated by the CICS SPE library
is CLASS=USER; this storage is released automatically at task termination.
L$UTFPE handles
floating-point error conditions such as overflow and underflow. L$UTFPE can
be invoked by character to floating-point conversion functions such as
strtod
and
sscanf
. The SPE L$UTFPE uses
bldexit
and the SPIE/ESPIE SVCs to
handle these conditions. Of course, other implementations may be possible
that do not rely on these SVCs. The sample code for L$UTFPE is not supported
in CICS.
Note that L$UTFPE uses the ESPIE macro only if the program
is executing in 31-bit addressing mode. This means that the ESPIE SVC is
never used under 370 mode CMS, which does not support the ESPIE SVC.
The L$UTFPE source module defines a function named L$CTFPE,
which is the name of the corresponding full library implementation. The full
library requires its own implementation of L$CTFPE and does not execute correctly
with the SPE implementation.
L$CTFPE is called as a normal C function. It is defined
as follows:
struct FPE {
union {
char space [12];
int active;
} hdr;
jmp_buf get_away;
};
void L$CTFPE(int func, struct FPE *elt);
The
func
argument to L$CTFPE is either 1, to define a
floating-point error trap, or 0, to cancel a previously defined trap.
When
func
is 1,
elt
addresses a trap element containing
work space and a jump buffer. L$CTFPE must set
elt->hdr.active
to a non-zero
value to indicate that the trap element is active. When a floating-point error
occurs, the defined trap should perform the following:
longjmp(elt->get_away, ic)
ic
is the program check interrupt code.
When
func
is 0,
elt
addresses the trap element for
the trap to be cancelled. L$CTFPE must reset
func->hdr.active
to 0 to show that
the trap has been cancelled. Note that the library routines that call L$CTFPE
always cancel traps in last-in, first-out order. Also note that only one
trap is ever defined at a time unless a function such as a math function,
is interrupted by a user
bldexit
routine that also calls a math function.
L$UTZON
is called by library timing functions to obtain the difference between Greenwich
time and local time. The timing routines assume that
time_t
values contain Greenwich
time and use the information returned by L$UTZON to convert them to local
time. L$UTZON supports several return value formats, since some information
is more readily available in some environments than in others.
The L$UTZON routine is also used in the SAS/C Generalized
Operating System (GOS) interface. The linkage conventions for L$UTZON in SPE
and GOS are similar enough that the same routine can be used for both. See
SAS Technical Report C-115, The Generalized Operating System Interface
for the SAS/C Compiler Run-Time System,
Release 5.50 for more information.
When L$UTZON is called, register 1 addresses a parameter
list in the format shown in L$UTZON Parameter List Format.
L$UTZON Parameter List Format
TZONPRMS DS 0D
DS A zero (nonzero for GOS)
DS A zero (can be used as a work area)
DS A address of a doubleword return value
Register 13 addresses a standard save area when L$UTZON
is called; however, it is not necessary to save and restore registers, as
this is done by the caller of L$UTZON.
When L$UTZON returns, the value in register 15 indicates
the format and meaning of the data addressed by the third word of the parameter
list.
If L$UTZON returns a code of 0, it stores a signed integer
in the first word of the return area, indicating the number of seconds difference
between local time and Greenwich time. For example, if it is 4 p.m. locally
when it is 2 p.m. Greenwich time, +7200 (2 hours in seconds) is stored.
If L$UTZON returns a code of 1, it stores a value in
TOD clock format in the return area, indicating the local time. More precisely,
this value represents the number of seconds since the local midnight of January
1, 1900, where bit 51 of the doubleword represents a microsecond.
If L$UTZON returns a code of 2, it stores the local
date and time in the return area in the format used by the OS TIME BIN macro.
More precisely, the first word of the doubleword should contain the local
time, expressed as the number of hundredths of a second since midnight, represented
as an unsigned binary integer. The second word of the doubleword should contain
the packed decimal local date in the form 00YYDDDF, where YY is the number
of years since 1900, and DDD is the number of days since January 1.
If L$UTZON cannot determine the local time offset, it
should return a code of 8 in register 15.
Some
SPE library functions, such as
memcpy
and
sqrt
, are designed to issue diagnostic
messages. In the SPE framework, the routine L$UWARN is called whenever a
diagnostic is appropriate. The SPE version of this routine simply stores
an appropriate value in
errno
and returns. Depending on the needs of the
application, some other action, such as issuing an abend or actually writing
a diagnostic, may be preferred.
L$UWARN can be called through either of its entry points,
#WARNING or $WARNING. When it is called, R1 addresses a variable length parameter
list in the format shown in L$UWARN Parameter List Format.
L$UWARN Parameter List Format
WARNPRMS DS 0D
DS F message number
DS F value to be stored in errno
EQU * zero or more replacement values
.
.
.
The first two words in the list are the diagnostic message
number and the value to be stored in
errno
. Any additional arguments represent
values to be inserted into the message text. (These values can be processed
using the
va_arg
macro.)
Two special
errno
values should be noted. If the value to be
stored in
errno
is 0, the diagnostic is a note rather than a warning, and
errno
should not be changed. If the value to be stored is negative, it indicates
a severe error, and an abend is recommended.
If L$UWARN is to write diagnostic messages, obtain the
message texts from the SASC.ERRMSGS data set (under OS/390) or LSU ERRMSGS
(under CMS). Each record in this file contains a message number in columns
1-8 and the corresponding message text beginning in column 9. The message
texts are suitable for use as formats with the
vsprintf
function.
After certain
error conditions, the library needs to abnormally terminate program execution.
For instance, if the program calls the POSIX
getpid
function, but USS
is not running, execution cannot continue because the function call cannot
succeed. However, the function definition does not provide a way for the function
to fail. The SPE library forces abnormal termination by calling the routine
L$UHALT. The supplied version of this routine simply issues the assembler
ABORT macro, which, in all systems other than CICS, issues an ABEND.
L$UHALT is called via the entry point L$CHALT. When
it is called, R1 addresses a parameter list in the format shown in L$UHALT Parameter List Format.
L$UHALT Parameter List Format
HALTPRMS DS 0D
DS F intended ABEND code
DS F message suppression flag
The first word in the argument list is the intended
ABEND code, an integer between 1200 and 1240. The second argument is an integer
which, if not zero, requests suppression of any library messages about the
ABEND. Since the SPE library does not diagnose ABENDs, this argument can
be ignored.
Note that if L$UHALT returns to its caller, the effects
of further execution are completely undefined.
Copyright © 2001
by SAS Institute Inc., Cary, NC, USA. All rights reserved.