[an error occurred while processing this directive]

HP OpenVMS Systems Documentation

Content starts here

HP OpenVMS MACRO Compiler
Porting and User's Guide


Previous Contents Index

2.3 Routine Calls and Declarations

The HP OpenVMS Calling Standard specifies very different calling conventions for OpenVMS VAX and OpenVMS Alpha and OpenVMS I64 systems.

On a OpenVMS VAX system, there are two different call formats, CALL and JSB, with five different instructions: CALLS, CALLG, JSB, BSBW, and BSBB. CALL instructions create a frame on the stack which contains return information, saved registers, and other routine information. Parameters to CALL instructions are passed in consecutive longword memory locations, either on the stack (CALLS) or elsewhere (CALLG). JSB instructions have the return address stored on the stack. For both call formats, the hardware processing of the calling instruction provides all of these functions.

On an OpenVMS Alpha system, there is only one call format and only one subroutine call instruction, JSR. For routines that expect parameters, the first six parameters are passed in R16 through R21, with the parameter count in R25 and subsequent parameters in quadwords on the stack. The hardware execution of the JSR instruction simply passes control to the subroutine and places the return address in a designated register. It neither allocates stack space, nor creates a call frame, nor provides any parameter manipulations. All of these functions must be done by the calling routine or the called routine.

On an OpenVMS I64 system, there is only one call format and only one subroutine call instruction, br.call. For routines that expect parameters, the first eight parameters are passed in R32 through R39, with the parameter count in R25 and subsequent parameters in quadwords on the stack. (Note that the MACRO compiler on OpenVMS I64 does not provide direct control over the register stack or any syntax for accessing registers greater than R31.) The hardware execution of the br.call instruction simply passes control to the subroutine, places the return address in a designated register, and adjusts the hardware register stack. It neither allocates stack space, nor creates a call frame, nor provides any parameter manipulations. All of these functions must be done by the calling routine or the called routine.

The compiler must generate code that conforms to the HP OpenVMS Calling Standard yet emulates the function of the different VAX MACRO instructions. This requires special code both at the calling instruction to manipulate the input parameters and at the entry point of the routine itself to create a stack frame and other context.

The compiler generates code only for source instructions that are part of a declared routine. For the compiler to generate the proper linkage information and proper routine code, you must insert a compiler directive at the entry points in the VAX MACRO source.

2.3.1 Linkage Section (OpenVMS Alpha only)

On Alpha systems, all external (out-of-module) references are made through a linkage section. The linkage section is a program section (psect) containing:

  • Addresses of external variables
  • Constants too large to fit directly in the code stream
  • Linkage pairs
  • Procedure descriptors

A linkage pair is a data structure used when making a call to an external module. The linkage pair contains the address of the callee's procedure descriptor, and the entry point address. The linkage pair resides in the caller's linkage section; therefore, there may be several linkage pairs for a particular routine, one in each caller's linkage section. Linkage pairs are not used for module-local calls, since the compiler can refer to the callee's frame descriptor directly.

A procedure descriptor is a data structure that provides basic information about a routine, such as register and stack usage. Each routine has one unique procedure descriptor, and it is located in its linkage section. The procedure descriptor is normally not accessed at run time. Its primary use is for interpreting the frame in case of exceptions, by the debugger, for example.

For more information about these and other data structures, see the HP OpenVMS Calling Standard.

2.3.2 Prologue and Epilogue Code

To mimic the behavior of VAX subroutines, the compiler must generate code at the entry and exit of each routine. This code emulates the functions that are done by the VAX hardware, and is called the prologue and epilogue code.

In the prologue code, the compiler must allocate stack space (and stacked registers on OpenVMS I64 systems) to save any necessary context. This includes saving any registers that will be preserved by the routine and saving the return address. If this is a CALL routine, it also includes establishing a new stack frame and changing the frame pointer (FP) (on OpenVMS Alpha systems, and on OpenVMS I64 systems, if needed), and may include further manipulation and storage of the input parameters (see Section 2.4).

In the epilogue code, the compiler must restore any necessary registers, stack frame information, and the return address, and trim the stack back to its original position.

2.3.3 When to Declare Entry Points

Any code label that is a possible target of a CALLS, CALLG, JSB, BSBW, or BSBB instruction must be declared as an entry point. In addition, any code label must be declared as an entry point using a .JSB_ENTRY or .JSB32_ENTRY directive if:

  • The label can be the target of a global (cross-module) JMP, BRB, or BRW instruction.
  • The label can be the target of an indeterminate branch (such as BRB @(R10)), where the address of the label has been stored in R10, even if the reference and the label are within the same module.
  • The address of the label is stored in a register or memory location, even if it is never accessed by the current module.

The OpenVMS calling standard for OpenVMS Alpha and OpenVMS I64 systems does not provide a way to access indeterminate code addresses directly. All such accesses are accomplished using a procedure descriptor to describe the routine and the code address. When a code label address is stored, the compiler does not know if that address will be referenced only by the current module, or whether it may be accessed by another MACRO module or another module written in another language. Whenever a source instruction stores a code address, the MACRO compiler instead stores the procedure descriptor address for that code address so that other code can access it correctly. For a procedure descriptor to exist, the label must be declared as an entry point. When a stored address is used as a branch destination, the compiler does not know where that address came from, so it always assumes that the stored address is the address of a procedure descriptor and uses that descriptor to pass control to the routine.

OpenVMS I64 systems behave identically, except the calling standard uses the term "function descriptor" and the function descriptors are created by the linker.

2.3.4 Directives for Designating Routine Entry Points

Macros in STARLET.MLB generate directives for designating the entry points to routines. See Appendix B for the format of each of these macros.

When assembled for OpenVMS VAX systems, the macros are null, except for .CALL_ENTRY. When compiled for OpenVMS Alpha or OpenVMS I64 systems, the macros expand to verify the arguments and generate the compiler directives. Therefore, you can use the following macros in common source modules:

  • The .CALL_ENTRY directive logically replaces the .ENTRY directive in Macro-32 code. However, you do not need to replace the .ENTRY directive unless you want to use one of the .CALL_ENTRY clauses. If you replace the .ENTRY directives, note that you cannot do a massive replace with an editor because the arguments do not match.

    Note

    Since the .ENTRY directive is converted to an OpenVMS Alpha or OpenVMS I64 .CALL_ENTRY directive, any registers modified in the routine declared by .ENTRY will be automatically preserved. This is different from VAX MACRO behavior. See Section 2.4.2.
  • The .JSB_ENTRY directive indicates a routine that is executed as a result of a JSB, BSBB, or BSBW instruction. The .JSB32_ENTRY directive is used for specialized environments that do not require the preservation of the upper 32 bits of Alpha or Itanium register values.
    The .JSB_ENTRY directive should also be used to declare entry points which are the targets of JMP or branch instructions from other modules, since such cross-module branches are implemented as JSBs by the compiler.
    Branches to stored code addresses, such as JMP (R0), are also treated as JSB instructions. Hence, potential targets must be declared with .JSB_ENTRY. The compiler warns you of this when you attempt to store a code label or when such a branch is used.
  • The .EXCEPTION_ENTRY directive (OpenVMS Alpha only) designates exception service routines.
  • The .CALL_LINKAGE directive (OpenVMS I64 only) associates a named or anonymous linkage with a routine name. When the compiler sees a CALLS, CALLG, JSB, BSBB, or BSBW instruction with the routine name as the target, it will use the associated linkage to decide which registers need to be saved and restored around the call.
  • The .USE_LINKAGE directive (OpenVMS I64 only) establishes a temporary named or anonymous linkage that will be used by the compiler for the next CALLS, CALLG, JSB, BSBB, or BSBW instruction processed in lexical order.
  • The .DEFINE_LINKAGE directive (OpenVMS I64 only) defines a named linkage that can be used with subsequent .CALL_LINKAGE or .USE_LINKAGE directives.

For more information about these directives, see Appendix B.

2.3.5 Code Generation for Routine Calls

The code generated for VAX JSB, BSBW, and BSBB instructions is very simple because no parameter manipulation must be done. However, the code for CALLS and CALLG instructions is more complex because the compiler must translate the parameter handling of the OpenVMS VAX calling standard to the OpenVMS Alpha calling standard or OpenVMS I64 calling standard.

When processing a CALLS instruction with a fixed argument count, the compiler automatically detects any pushes onto the stack that are actually passing parameters to a routine, and generates code that moves the parameters directly to the registers used by the OpenVMS Alpha calling standard or OpenVMS I64 calling standard.

Consider the following VAX call:


PUSHL   R2
PUSHL   #1
CALLS   #2,XYZ

This VAX call is compiled as follows on OpenVMS Alpha:


SEXTL   R2, R17                  ; R2 is the second parameter
LDA     R16, 1(R31)              ; #1 is the first parameter
LDA     R25, 2(R31)              ; 2 parameters
LDQ     R26, 32(R13)             ; Get routine address
LDQ     R27, 40(R13)             ; Get routine linkage pointer
JSR     R26, R26                 ; Call the routine

This VAX call is essentially compiled as follows on OpenVMS I64:


sxt4    r46 = r28                  ; Load 2nd output register
adds    r45 = 1, r0                ; Load 1st output register
adds    r25 = 2, r0                ; 2 parameters
adds    r12 = -16, r12             ; Extra octaword per calling standard
br.call.sptk.many br0 = XYZ ;;     ; Call the routine
adds    r12 = 16, R16              ; Pop extra octaword
mov     r1 = r32                   ; Restore saved GP

The exact code generated might be different in order to adjust for differences between the OpenVMS Alpha calling standard and the OpenVMS I64 calling standard.

For a CALLS instruction with a variable argument count or a CALLG instruction, the compiler must call an emulation routine which unpacks the argument list into the OpenVMS Alpha or OpenVMS I64 format. This has two side effects. On OpenVMS VAX systems, if part of the argument list pointed to by a CALLG instruction is inaccessible, but the called routine does not access that portion of the argument list, the routine will complete successfully without incurring an access violation. On OpenVMS Alpha or OpenVMS I64 systems, since the entire argument list is processed before calling the routine, the CALLG will always incur an access violation if any portion of the argument list is inaccessible. Also, if either the argument count of a CALLG instruction or a CALLS instruction with a variable argument count exceeds 255, the emulated call returns with an error status without ever invoking the target routine.

2.4 Declaring CALL Entry Points

Because of the differences between the OpenVMS VAX, OpenVMS Alpha, and OpenVMS I64 calling standards (see Section 2.3), the compiler must translate all VAX parameter references to AP in the following ways:

  • It converts parameter references off AP in the called routine into direct register and stack references to the new OpenVMS Alpha or OpenVMS I64 parameter locations.
  • It detects references to AP that may result in aliased references to the parameter list, or that use variable offsets which cannot be resolved at compile time. The compiler mimics VAX argument lists by packing the quadword register and stack arguments into a longword argument list on the stack. Two arguments to the .CALL_ENTRY directive have been created for this purpose (see Section 2.4.1).

In the latter case, the practice is known as homing the argument list. The resulting homed argument list is in a fixed position in the stack frame, so all references are FP-based.

2.4.1 Homed Argument Lists

Examples of AP references for which the compiler automatically homes the arguments include:

  • Storing AP or an address based on AP in another register
  • Passing AP or an address based on AP as a parameter
  • Adding a variable offset to AP to reference a parameter
  • Using variable indexing off AP to reference a parameter
  • Using a non-longword aligned offset into the arglist, such as 6(AP)

The compiler sets up the homed argument list in the fixed temporary region of the procedure frame on the stack.

Although not mandatory, you can specify homing with two arguments to the .CALL_ENTRY directive, home_args=TRUE and max_args=n.

The argument max_args=n represents the maximum number of longwords the compiler should allocate in the fixed temporary region. The main reason for using max_args=n is if your program receives more arguments than the number explicitly used in the source code. A common reason for specifying home_args=TRUE in a routine, which does not in itself require it, is the invocation of a JSB routine that references AP. For such references, the compiler assumes that the argument list was homed in the last .CALL_ENTRY routine, and uses FP-based references. Because this may not always be a valid assumption, the compiler reports an informational message when AP is used inside .JSB_ENTRY routines.

If you want to suppress the informational messages that the compiler reports, use both arguments.

If you omit one or both arguments and the compiler detects a use that requires homing, messages are issued by the compiler, informing you of what the compiler did. You can then make the appropriate changes to your code, such as adding one or both arguments or changing the value of the max_args argument.

If you only specify home_args=TRUE, the following message is issued:


AMAC-W-MAXARGEXC, MAX_ARGS exceeded in routine routine_name, using n

This message is issued because you explicitly asked for homing but did not specify the number of arguments with max_args. If the compiler does not find any explicit argument reference, then it allocates six longwords on OpenVMS Alpha systems and eight longwords on OpenVMS I64 systems. If the compiler finds explicit argument references, it allocates the same number of longwords as the highest argument reference found. (This message is also issued if you specify a value for max_args which is less than the number of arguments found by the compiler to be homed.)

If you specify only max_args, and if the compiler detects a reference requiring homing, the compiler issues the following message:


AMAC-I-ARGLISHOME, argument list must be homed in caller

If you specify neither argument and the compiler detects a reference requiring homing, the compiler issues the following messages:


AMAC-I-ARGLISHOME, argument list must be homed in caller
AMAC-I-MAXARGUSE, max_args value used for homed arglist is n

where n represents the highest argument referenced, as detected by the compiler.

2.4.2 Saving Modified Registers

A well-behaved VAX MACRO CALL routine passes all parameters via the argument list and saves all necessary registers via the register mask in the .ENTRY declaration. However, some routines do not adhere to these standards and may pass parameters via registers or save and restore the contents of the necessary registers within the routine with instructions such as PUSHL and POPL.

Using PUSHL and POPL to save and restore registers on an OpenVMS Alpha or OpenVMS I64 system is insufficient because these instructions save only the low 32 bits of the register. To avoid register corruption, the compiler automatically saves and restores the full 64-bit values of any register modified within the routine (except R0 and R1), in addition to the registers specified in the register save mask. This means that any registers that were intended to pass an output value out of the called routine will no longer do so. You must either pass the output via the standard argument list or declare the register to be output with the output parameter in the .CALL_ENTRY declaration (see Section 2.6). On OpenVMS I64 systems, you must use the .CALL_LINKAGE directive when calling such a routine since the I64 calling standard forces the compiler to save various registers around the call.

2.4.3 Modifying the Argument Pointer

If a routine modifies AP, the compiler changes all uses of AP in that routine to R12 and reports all such modifications. Although traversing the argument list in this way is not supported, you can obtain similar results by explicitly moving the address 0(AP) to R12 and specifying home_args=TRUE in the entry point. R12 will point to a VAX format argument list.

2.4.4 Establishing Dynamic Condition Handlers in Called Routines

To establish a dynamic condition handler in a called routine, it is necessary to store a condition handler address in the frame. The compiler generates a static condition handler for .CALL_ENTRY routines that modify 0(FP). The static handler invokes the dynamic handler or returns if no condition handler address has been stored in the frame.

For performance reasons, on OpenVMS Alpha systems, the compiler does not automatically insert a TRAPB instruction at the end of every routine that establishes a condition handler. Because of the indeterminate nature of traps on an Alpha system, this could allow traps from instructions near the very end of the routine to be processed after the frame pointer has been changed in the routine epilogue code, with the result that the declared handler would not process the trap. If it is essential that any traps generated in the routine be processed by the declared handler, insert an EVAX_TRAPB built-in immediately before the RET instruction that ends the routine.

2.5 Declaring JSB Routine Entry Points

In assembled VAX MACRO code and compiled OpenVMS Alpha and OpenVMS I64 object code alike, JSB routine parameters are typically passed in registers.

A JSB routine that writes to registers sometimes saves and restores the contents of those registers, unless the new contents are intended to be returned to a caller as output. However, because it is fairly common for a VAX MACRO module to save and restore register contents by issuing instructions such as PUSHL and POPL, saving only the low 32 bits of the register contents, the compiler must add 64-bit register saves/restores at routine's entry and exit points.

The compiler can compute the set of used registers, but without complete knowledge of the routine's callers (which may be in other modules), it cannot determine which registers are intended to contain output values. For this reason, the user must add, at each JSB routine entry point, a .JSB_ENTRY or a .JSB32_ENTRY directive that declares the routine's register usage.

2.5.1 Differences Between .JSB_ENTRY and .JSB32_ENTRY

The compiler provides two different ways of declaring a JSB entry point. The .JSB_ENTRY directive is the standard declaration, and the .JSB32_ENTRY directive is provided for cases when you know that the upper 32 bits of the 64-bit register values are never required to be preserved by the routine's callers.

In routines declared with the .JSB_ENTRY directive, the compiler, by default, saves at routine entry and restores at routine exit the full 64-bit contents of any register (except R0 and R1) that is modified by the routine. If a register is not explicitly modified by the routine, the compiler will not preserve it across the routine call. You can override the compiler's default behavior by means of register arguments that can be specified with the .JSB_ENTRY directive, as described in Section 2.6.

When .JSB32_ENTRY is used, the compiler does not automatically save or restore any registers, thus leaving the current 32-bit operation untouched. The compiler will only save and restore the full 64-bit value of registers you explicitly specified in the preserve argument.

If the routine you are porting is in an environment where you know that no caller ever needs to have the upper 32 bits of a register preserved, it is recommended that you use the .JSB32_ENTRY directive. It can be much faster for porting code because you do not have to examine the register usage of each routine. You can specify the .JSB32_ENTRY directive with no arguments, and the existing 32-bit register push/pop code will be sufficient to save any necessary register values.

Note

The OpenVMS Alpha or OpenVMS I64 compilers for other languages may use 64-bit values. Therefore, any routine which is called from another language --- or which is called from another MACRO routine that is in turn called from another language --- cannot use .JSB32_ENTRY.


Previous Next Contents Index