HP OpenVMS Systems Documentation

Content starts here

HP OpenVMS MACRO Compiler
Porting and User's Guide


Previous Contents Index

3.3.3 Pushing a Return Address onto the Stack

The compiler detects any attempt to push an address onto the stack (for instance, PUSHAB label) to cause a subsequent RSB to resume execution at that location and flags this practice as an error. (On OpenVMS VAX systems, the next RSB would return to the routine's caller.)

Recommended Change

Remove the PUSH of the address, and add an explicit JSB to the target label just before the current routine's RSB. This will result in the same control flow. Declare the target label as a .JSB_ENTRY point.

For example, the compiler would flag the following code as requiring a source change.


Rout:   .JSB_ENTRY
        .
        .
        PUSHAB  continue_label
        .
        .
        RSB

By adding an explicit JSB instruction, you could change the code as follows. Note that you would place the JSB just before the RSB. In the previous version of the code, it is the RSB instruction that transfers control to continue_label, regardless of where the PUSHAB occurs. The PUSHAB is removed in the new version.


Rout:   .JSB_ENTRY
        .
        .
        .
        JSB     continue_label
        RSB

3.3.4 Removing the Return Address from the Stack

The compiler detects the removal of the return address from the stack (for instance, TSTL (SP)+) and flags this practice as an error. The removal of a return address in VAX code allows a routine to return to its caller's caller.

Recommended Change

Rewrite the routine so it returns a status value to its caller that indicates that the caller should return to its caller. Alternatively, the initial caller could pass the address of a continuation routine, to which the lowest-level routine can JSB. When the continuation routine RSBs back to the lowest-level routine, the lowest-level routine RSBs.

For example, the compiler would flag the following code as requiring a source change:


Rout1:  .JSB_ENTRY
        .
        .
        JSB   Rout2
        .
        RSB
Rout2:  .JSB_ENTRY
        .
        .
        JSB   Rout3       ; May return directly to Rout1
        .
        RSB
Rout3:  .JSB_ENTRY
        .
        .
        TSTL  (SP)+       ; Discard return address
        RSB               ; Return to caller's caller

You could rewrite the code to return a status value, as follows:


Rout2:  .JSB_ENTRY
        .
        .
        JSB          Rout3
        BLBS R0, No_ret   ; Check Rout3 status return
        RSB               ; Return immediately if 0
No_ret: .
        .
        RSB
Rout3:  .JSB_ENTRY
        .
        .
        CLRL  R0          ; Specify immediate return from caller
        RSB               ; Return to caller's caller

3.3.5 Modifying the Return Address

The compiler detects any attempt to modify the return address on the stack and flags it as an error.

Recommended Change

Rewrite the code that modifies the return address on the stack to return a status value to its caller instead. The status value causes the caller to either branch to a given location or contains the address of a special .JSB_ENTRY routine the caller should invoke. In the latter case, the caller should RSB immediately after issuing the JSB to a special .JSB_ENTRY routine.

For example, the compiler would flag the following code as requiring a source change.


Rout1:  .JSB_ENTRY
        .
        .
        JSB  Rout2                    ; Might not return
        .
        RSB
Rout2:  .JSB_ENTRY
        .
        .
        MOVAB continue_label, (SP)   ; Change return address
        .
        RSB

You could rewrite the code to incorporate a return value as follows:


Rout1:  .JSB_ENTRY
        .
        .
        JSB   Rout2
        TSTL  R0                      ; Check for alternate return
        BEQL  No_ret                  ; Continue normally if 0
        JSB   (R0)                    ; Call specified routine
        RSB                           ; and return
No_ret: .
        .
        RSB
Rout2:  .JSB_ENTRY
        CLRL  R0
        .
        .
        MOVAB  continue_label, R0    ; Specify alternate return
        RSB

3.3.6 Coroutine Calls

Coroutine calls between two routines are generally implemented as a set of JSB instructions within each routine. Each JSB transfers control to a return address on the stack, removing the return address in the process (for instance, (JSB @(SP)+). The compiler detects coroutine calls and flags them as errors.

Recommended Change

You must rewrite the routine that initiates the coroutine linkage to pass an explicit callback routine address to the other routine. The coroutine initiator should then invoke the other routine with a JSB instruction.

For example, consider the following coroutine linkage:


Rout1:  .JSB_ENTRY
        .
        JSB Rout2   ; Rout2 will call back as a coroutine
        .
        JSB @(SP)+  ; Coroutine back to Rout2
        .
        RSB
Rout2:  .JSB_ENTRY
        .
        JSB @(SP)+  ; coroutine back to Rout1
        .
        RSB

You could change the routines participating in such a coroutine linkage to exchange explicit callback routine addresses (here, in R6 and R7) as follows:


Rout1:  .JSB_ENTRY
        .
        .
        MOVAB Rout1_callback, R6
        JSB Rout2
        RSB
Rout1_callback: .JSB_ENTRY
        .
        .
        JSB  (R7)    ; Callback to Rout2
        .
        RSB
Rout2:  .JSB_ENTRY
        .
        .
        MOVAB Rout2_callback, R7
        JSB (R6)    ; Callback to Rout1
        RSB
Rout2_callback: .JSB_ENTRY
        .
        RSB

To avoid consuming registers, the callback routine addresses could be pushed onto the stack at routine entry. Then, JSB @(SP)+ instructions could still be used to perform "direct" JSBs to the callback routines. In the following example, the callback routine addresses are passed in R0, but pushed immediately at routine entry:


Rout1:  .JSB_ENTRY
        .
        .
        MOVAB Rout1_callback, R0
        JSB rout2
        RSB
Rout1_callback: .JSB_ENTRY
        PUSHL   R0      ; Push callback address received in R0
        .
        .
        JSB     @(SP)+  ; Callback to rout2
        .
        .
        RSB
Rout2:  .JSB_ENTRY
        PUSHL   R0      ; Push callback address received in R0
        .
        .
        MOVAB Rout2_callback, R0
        JSB @(SP)+      ; Callback to Rout1
        RSB
Rout2_callback: .JSB_ENTRY
        .
        .
        RSB

3.3.7 Using REI to Change Modes

A common VAX use of the REI instruction is to change modes by pushing an explicit target PC and PSL on the stack. This code cannot be compiled without some changes to the source code on OpenVMS Alpha or OpenVMS I64 systems for the following reasons:

  • The destination code on OpenVMS Alpha requires that a linkage section pointer be established. The destination code on OpenVMS I64 requires that a GP be established. REI does not provide a way to do these.
  • An REI frame on an Integrity server is more complex than on an Alpha or VAX system and includes saved general registers, other registers, and information about the register stack. (Similarly, an REI frame on an Alpha system is more complex than on a VAX system and includes saved registers.)
    The prologue code on Itanium is required to establish a register frame.
    In addition, on Itanium and Alpha, all subroutines have epilogue code to restore saved, nonscratch registers. A new syntax would be necessary to accommodate register passing and restoration.
  • The mode change means that the process will be executing on a different stack at the target. This places new requirements on cleaning up the previous stack.

Recommended Change

A system routine, EXE$REI_INIT_STACK, has been created to perform the corresponding function for OpenVMS Alpha or OpenVMS I64 systems. This routine accepts the new mode and a callback routine address as parameters. This routine has the advantage of being usable from higher-level languages as well.

You must restructure existing code so that this routine call can be used. The code label where execution is to continue must be declared with an entry point directive, since it will be called by the system routine.

The following examples show two ways that the REI instruction is used in VAX MACRO code for changing modes and one way to accomplish the same thing using the EXE$REI_INIT_STACK routine for OpenVMS Alpha or OpenVMS I64:

Before (1)


PUSHL  new_PSL
PUSHL new_PC
REI

Before (2)


PUSHL     new_PSL
JSB       10$
 .
 .
 .
CONTINUE                   ;With new PSL
 .
 .
 .
10$:  REI

After


PUSHL     Continuation_routine
PUSHL     new_mode         ;Not a PSL
CALLS #2, exe$rei_init_stack
 .
 .
 .
Continuation routine:  .JSB_ENTRY

When your program reaches the continuation routine, it will be executing in a new mode at a new location and the old mode stack will be reinitialized. The memory stack and (on OpenVMS I64) the register stack will be set to their bases for the new mode.

Note that there is the issue of passing arguments to the outer mode routine. On OpenVMS Alpha systems, most registers are preserved across REI. On OpenVMS I64 systems, only R26, R27, and R10 (corresponding to Alpha registers R8, R9, and R10) are preserved. If you need to pass more data, you cannot make a data structure on the inner mode stack and pass the address to the outer mode routine through a register, because the inner mode stack is reset to its base by EXE$REI_INIT_STACK. You should instead allocate space on the outer mode stack (using MFPR_xSP and MTPR_xSP) and place the data there.

3.3.8 Loop Nesting Limit

The compiler must identify loops in program code in order to generate code correctly. A loop is a "nested" loop if it is inside another loop or if it partially overlaps another loop. If loops are nested more than 32 levels deep, the compiler will report a fatal error, and compilation will terminate. The VAX MACRO assembler did not need to identify loops, and so did not enforce such a restriction.

Recommended Change

If the compiler reports this error, restructure the code so that the program does not exceed the limit.

3.4 Dynamic Image Relocation

On OpenVMS VAX systems, you can copy position independent code (PIC) from one address range to another, and it assembles and runs correctly. If you compile this code for OpenVMS Alpha or OpenVMS I64, which includes a range of code that you copied using source code labels, it does not work for two reasons. First, the compiled code may not be in the same order as the source code. Second, on OpenVMS Alpha, the code requires the linkage section to make external references. Not only is the linkage section in another psect, but it also contains absolute addresses fixed up by the linker and image activator. On OpenVMS I64, the code requires a portion of the global region that was built by the linker and image activator.

Recommended Change

Replicate the code or otherwise eliminate the copy of code from one address range to another.

3.5 Overwriting Static Data

The VAX MACRO assembler allows complete freedom in overwriting previously initialized static storage. This is usually done by changing the current location counter with a ".=" construct.

By contrast, the MACRO compiler restricts overwriting so that partial overwriting of an existing data item is not allowed, except for .ASCII data. You can overwrite:

  • Any scalar item with another of the same size
  • Any storage left blank (declared with .BLKx or ".=.+n")
  • Sections of .ASCII data with other .ASCII or .BYTE directives

Recommended Change

Where possible, change your code to one of the following forms that is allowed:

  • Code that overwrites data declared by any of the scalar storage directives (.BYTE, .WORD, or .LONG, and so forth) using a directive of the same type or one that occupies the same number of bytes. The items must be the same size at the same location; they cannot partially overlap. For instance:


    LAB1:   .WORD 1
            .WORD 2
            .=LAB1
            .WORD 128
    
  • Partial overwriting of .ASCII data
    You can overwrite portions of previously written .ASCII data using .ASCII or .BYTE. (Since .ASCIC and .ASCIZ are implemented as a pair of .ASCII/ .BYTE directives, they can also overwrite .ASCII).
    For example:


    DATA:  .ascii /abcdefg/
    .=data
        .ascii /z/           ; change "a" to "z"
    .=data
        .byte   0            ; change "z" to 0
    .=data+4
        .ascii /xyz/         ; change "efg" to "xyz"
    

    The new data must be completely within the bounds of the previous .ASCII string. The following is illegal:


    DATA:  .ascii /abcdefg/
    .=data+4
           .ascii /lmnop/       ; exceeds end of previous .ASCII
    

    Partial overwriting with other directive types (.LONG, and so forth) is not allowed.

3.6 Static Initialization Using External Symbols

Some forms of static initialization using external symbols are not allowed.

Recommended Change

Where possible, change your code to one of the forms that is allowed. This can often be done by defining additional symbols using $xxxDEF macros. On OpenVMS Alpha and OpenVMS I64 systems, the compiler supports expressions of the form:


<symbol1 +/- offset1> OPR <symbol2 +/- offset2>

where OPR can be:

+ Add
- Subtract
* Multiply
/ Divide
@ Arithmetic shift
& Logical inclusive AND
! Logical inclusive OR
\ Logical exclusive OR

Symbol1 and symbol2 (both optional) are external values or labels in another psect, and offset1 and offset2 can be expressions but must resolve to compile-time constants.

On OpenVMS I64 systems, due to the design of the ELF object file format, the linker will only allow subtraction when symbol1 or symbol2 resolves to the address of a routine.

The compiler does not support expressions of the form:


<symbol1> OPR <symbol2>

If a static initialization expression cannot be reduced to this form, the compiler will report an illegal static initialization. Use brackets to ensure correct operator precedence.

3.7 Transfer Vectors

The compiler flags any occurrence of the .TRANSFER directive in VAX MACRO code as an error unless you have specified /noflag=directives. In that case, no message is reported, but the .TRANSFER directive is ignored.

On OpenVMS VAX systems, you can establish universal entry points for relocatable code in shareable images by explicitly defining transfer vectors in VAX MACRO source code. On OpenVMS Alpha and OpenVMS I64 systems, you establish them by declaring the symbol values of universal entry points in a linker options file. The linker constructs a symbol vector table within the shareable image that allows external images to locate relocatable universal procedure entry points and storage addresses.

Recommended Change

You must remove the transfer vector from the VAX MACRO source. When linking the object file produced by the compiler, you must specify a linker options file that includes the SYMBOL_VECTOR statement. Within the SYMBOL_VECTOR statement, you must list each universally-visible, relocatable symbol (procedure entry point or data address), indicating whether each is DATA or a PROCEDURE.

Note that the linker builds the symbol vector in the order in which the symbols appear in the linker options file. You must retain this symbol order over the course of later builds of the shareable images. That is, you can add entries to the end of the symbol list or remove entries, but ongoing entries must keep the same ordinal position in the list. For more information about transfer vectors, refer to the HP OpenVMS Linker Utility Manual.

3.8 Arithmetic Exceptions

On OpenVMS Alpha systems, the treatment of arithmetic exceptions is different from and incompatible with that of OpenVMS VAX systems. Exception handlers designed for an OpenVMS VAX system that field arithmetic exceptions will be unable to match the expected signal names with those actually signaled on OpenVMS Alpha systems.

On OpenVMS VAX systems, the architecture ensures that arithmetic exceptions are reported synchronously, whereas on OpenVMS Alpha systems, arithmetic exceptions are reported asynchronously. On a OpenVMS VAX system, a VAX arithmetic instruction that causes an exception (such as an overflow) enters any exception handlers immediately and no subsequent instructions are executed. The program counter (PC) reported to the exception handler is that of the failing arithmetic instruction. This allows application programs, for example, to resume the main sequence, with the failing operation being emulated or replaced by some equivalent or alternate set of operations.

On OpenVMS Alpha systems, a number of instructions (including branches and jumps) can execute beyond that which caused the exception. These instructions may overwrite the original operands used by the failing instruction, therefore causing information integral to interpreting or rectifying the exception to be lost. The PC reported to the exception handler is not that of the failing instruction, but rather is that of some subsequent instruction. When the exception is reported to an application's exception handler, it may be impossible for the handler to fix up the input data and restart the instruction.

Because of this fundamental difference in arithmetic exception reporting, the OpenVMS Alpha operating system defines a new, single condition code, SS$_HPARITH, to indicate all of the arithmetic exceptions. For information about the SS$_HPARITH exception signal array, see Migrating to an OpenVMS AXP System: Recompiling and Relinking Applications.1

Recommended Change

If the condition handling routine in your application only counts the number of arithmetic exceptions that occurred, or aborts when an arithmetic exception occurs, it does not matter that the exception is delivered asynchronously on Alpha. These condition handling routines require only the addition of a test for the SS$_HPARITH condition code. The VAX arithmetic exceptions will never be returned on OpenVMS Alpha (except from translated VAX images). If your application attempts to restart the operation that caused the exception, you must either rewrite your code or use a compiler qualifier or directive that ensures the exact reporting of arithmetic exceptions. Note, however, that the compiler must drain the instruction pipeline after using each arithmetic instruction to provide this function, which severely affects performance.

The EVAX_TRAPB built-in can be used to force any preceding traps to be signaled. Because of the performance impact of this built-in, it should be used only after arithmetic instructions you need to isolate.

Note

1 This manual has been archived but is available on the OpenVMS Documentation CD-ROM.

3.9 Page Size

A set of macros has been developed which can be used to standardize the way page size dependencies are coded and to make future changes easier. These are discussed in Appendix D.

3.10 Locking Pages into a Working Set

Pages are commonly locked down in the following ways:

  • At image initialization, locking a section of code for the life of the image. This method uses the $LKWSET system service call to lock the pages in memory. The $LKWSET call is often in a different module from the code to be locked.
  • On-the-fly, locking down a small section of code for a specific operation. This method often uses the poor programmer's lockdown method of raising IPL and specifying the IPL as a data location at the end of the code to be locked.

On an OpenVMS Alpha system, not only must the code pages be locked in memory, but the code's linkage section must also be locked. Because of this and other constraints, the LOCK_SYSTEM_PAGES, UNLOCK_SYSTEM_PAGES, PMLREQ, and PMLEND macros do not exist on OpenVMS Alpha systems.

On OpenVMS I64 systems, $LKWSET has been modified to avoid the problems encountered on OpenVMS Alpha systems by locking the entire image referenced by the parameters. This insures correct behavior, but might result in a performance penalty. See the OpenVMS I64 Release Notes for more information.

Recommended Change

To minimize code changes, both cases are accommodated by means of several new macros.

For lockdown done at image initialization time, three macros are provided: two as "begin" and "end" markers to delimit code to be locked, and an additional initialization macro to create the descriptors and issue the $LKWSET calls.

To lock and unlock code where poor programmer's lockdown or other on-the-fly lockdown was done, two other macros are provided. For architectural reasons discussed in this section, these macros also use the $LKWSET and the $ULWSET system service calls to lock and unlock pages.

These macros reside in LIB.MLB, since anyone needing to lock pages into the working set should already be executing privileged code.

Note

These two methods (lockdown done at image initialization time and on-the-fly lockdown) cannot be combined in one image. The $ULWSET service issued by the on-the-fly lockdown will also unlock the section that was locked at initialization time. You must choose one method or the other for the entire image. Be particularly careful in images that contain modules compiled from different source languages.

How the OpenVMS Alpha and OpenVMS I64 Architectures Affect Locking Pages

Locking pages into a working set is much more complicated on an OpenVMS Alpha or OpenVMS I64 system than it is on a OpenVMS VAX system, for these reasons:

  • It is not sufficient to lock the code in memory. Since the code will make references to its linkage section for various values and routine addresses, the linkage section must also be locked. Note: The linkage section on OpenVMS Alpha corresponds to, on OpenVMS I64, the Global Data segment or the GP area. The GP area is the data area for an entire program, not just one routine as on OpenVMS Alpha.
  • Due to the optimization that is done by the compiler, it is not sufficient to put labels before and after the code to be locked---the compiler may move some of the code between the labels to an area outside of their scope.
  • The poor programmer's method of raising IPL and specifying the IPL as a data location at the end of the code to be locked does not work for these reasons as well as for the following reasons:
    • The sequence requires the execution of multiple instructions, making it impossible to reference the IPL and raise IPL atomically.
    • OpenVMS Alpha and OpenVMS I64 compiler technology prevents code and data (for example, the longword containing the IPL) from being in the same program section.

The only way to lock pages into the working set on OpenVMS Alpha or OpenVMS I64 is to call the system service $LKWSET.

Using Psects to Delineate Code

The macros provided here use psects to enclose the section of code to be locked. The macros create three psects, name them sequentially, and use them in the following way:

  • The beginning and ending labels of the code are defined in the first and last psects, respectively
  • The code itself is put in the middle psect

Provided that the attributes of all psects are the same, the linker will place them sequentially in the image. The same thing must be done for the linkage section, which requires a second $LKWSET call.

Since all code to be locked anywhere in the image goes into the same psect, this method of using psects has the side effect of locking all lockable code when any requester locks any section. This is probably advantageous; OpenVMS Alpha or OpenVMS I64 pages are at least 8KB and most requesters are locking a pagelet or less, so most of the time all of the code to be locked will fit on a single page.


Previous Next Contents Index