Writing VMS Privileged Code
Part IV: Porting MACRO-32 code to Alpha AXP
Edward A. Heinrich
The first three installments of this series presented the concepts used in programs designed to exploit features in the OpenVMS operating system. Before continuing on, we will take a quick examination of some of the differences between the VAX and AXP architectures that affect systems programmers.
Before either undertaking a port of existing MACRO-32 code to AXP or coding new routines designed to execute in both environments, we suggest you obtain the manual “Migrating to an OpenVMS Alpha System: Porting VAX MACRO Code”, order number: xx-yyy-vv from Digital. Another very useful publication, particularly for programmers interested in OpenVMS AXP device drivers, is the “OpenVMS AXP Device Support: Creating a Step 1 Driver from an OpenVMS VAX Device Driver”, order number: AA-PV73A-TE. Step 1 drivers are device drivers for OpenVMS AXP V1.0 and V1.5. Step 2 drivers are device drivers for all versions of OpenVMS AXP beyond those; a preliminary Step 2 driver manual is available via anonymous ftp from gatekeeper.dec.com.
Although the OpenVMS AXP development team is promising that a future release will allow access to all features of the operating system from the C language, on an OpenVMS VAX system, only MACRO-32 and BLISS code can call many of the executive routines. Currently, in both environments, many of the internal data structures are only defined for MACRO-32 and BLISS code in the LIB.MLB and LIB.REQ files in SYS$LIBRARY. Digital, unfortunately, has always charged a high price for the BLISS compiler, and, as a result, there has not been a widespread implementation of software written in BLISS outside of Digital products. We personally view this a mistake on Digital’s part, since BLISS is the perfect language for coding privileged OpenVMS utilities, and many BLISS programs written on VAXen compile and execute without change on AXP.
As a result of the disparity, programmers wishing to maintain a single source file that can execute on both architectures are pretty much restricted to MACRO-32. Since many sites will, initially at least, contain a mix of VAX and AXP systems, you will likely want to write code that can execute in either environment. In this article, we will demonstrate some techniques that we have used to maintain a single source file that can be built for either environment.
Determining the System Type
First, we need to ascertain the processor type for which the image is targeted. Although VMS is VMS, when it comes to privileged-mode programming, there are some cases where routines have different names, different algorithms are used, or functionality is either missing or superceded by a new mechanism. In addition, some of the Digital-supplied macros for architecture-dependent operations, such as calculating page boundaries, rely on certain symbols being defined, depending on the architecture.
Digital has suggested creating an ARCH_DEFS.MAR file and inserting it in your SYS$LIBRARY:STARLET.MLB macro library file.
On AXP machines the following definitions should be in that file:
EVAX = 1 ALPHA = 1 BIGPAGE = 1 ADDRESSBITS = 32
While on a VAX, the file should contain the following symbols:
VAX = 1 VAXPAGE = 1 ADDRESSBITS = 32
While this method may work on many systems, it is not infallible, since there are no guarantees that this file exists in the STARLET.MLB library on every system on which you may wish to build your program. An easier way is to include a few lines of conditional assembly code that can figure out the target environment and correctly define the symbols when the module is assembled or compiled.
A better way is to use a trick picked up from a discussion on DECUServe (thanks to Ray Whitmer of WordPerfect). The .NTYPE directive in the MACRO-32 assembler and compiler will return the addressing mode for an operand. The trick is to use .NTYPE with an AXP-only register name, for example, R31, and check the addressing mode that’s returned. Using the MACRO-32 compiler (on AXP), .NTYPE will return a value indicating that it’s a hardware register. The MACRO-32 assembler on a VAX would return a value indicating that it’s a symbolic memory reference, since there is no R31 on a VAX.
The following code fragment checks to see if AXP register R31 exists and ensures that it is a hardware register. If so, the symbol ALPHA is defined; if R31 does not exist, or it is not a hardware register, the ALPHA symbol is left undefined. Subsequent code that needs to be conditionalized for architectural differences can isolate that code using .IF DEFINED ALPHA or .IF NOT_DEFINED ALPHA conditional assembly blocks.
; ; Determine if we are on an AXP machine: ; .NTYPE ...IS_IT_AXP, R31 ; Is R31 defined? ...IS_IT_AXP=<...IS_IT_AXP@-4&^XF>-5 ; Is it a hardware register? .IIF EQ, ...IS_IT_AXP, ALPHA=1 ; Then define the ALPHA symbol ; ; Next define the appropriate symbols ; .IF DEFINED ALPHA ; Make this a little less messy EVAX = 1 BIGPAGE = 1 ADDRESSBITS = 32 .IF_FALSE ; For a VAX VAX = 1 VAXPAGE = 1 ADDRESSBITS = 32 .ENDC ; Architecture symbols
For example, the following .IF/.IFF directives delimit the AXP and VAX code.
.IF DEFINED ALPHA ....AXP-specific code/data.... .IFF ....VAX-specific code/data.... .ENDC
For single instructions or directives that may be necessary for AXP (e.g., .JSB_ENTRY), you can use the shorter .IIF directive and the DEFINED abbreviation, DF:
.IIF DF,ALPHA, .JSB_ENTRY ; Declare JSB entry
To make the code look less cluttered, you might want to create a series of short macros that expand to the correct directives, etc. For example, the MACRO-32 compiler includes the directive .BRANCH_LIKELY so you can supply hints to the compiler about whether or not a branch at a given point is likely. By supplying the hint, the compiler can produce better code for the most likely case. An example of .BRANCH_LIKELY is:
CMPL #RMS$_EOF,R0 ; Was status end-of-file? .IFF DF,ALPHA, .BRANCH_LIKELY ; Branch is likely BNEQU 10$ ; Branch if not MOVL #SS$_NORMAL,R0 ; Set status to success RET ; ... and return 10$:
You could create a macro called BRANCH_LIKELY that would make the code above a little easier to read:
.MACRO BRANCH_LIKELY .IF DEFINED ALPHA .BRANCH_LIKELY .ENDC .ENDM BRANCH_LIKELY [...] CMPL #RMS$_EOF,R0 BRANCH_LIKELY BNEQU 10$ [...]
When assembled on a VAX, the BRANCH_LIKELY macro produces nothing. Though simple, the macro does help keep the main code from appearing too cluttered with .IFs all over.
Avoid the use of the .LINK Directive
Often in MACRO-32 code, a programmer includes a .LINK directive to automatically tell the linker to include another object module or symbol table when resolving external symbol references. The .LINK directive is frequently used in privileged-mode modules to guarantee that the system symbol table, SYS$SYSTEM:SYS.STB, is referenced, even if the person performing the link has no idea that the module needs to be linked with that table.
When MACRO-32 code is compiled on an AXP machine, the compiler ignores the .LINK directive. There is no warning message—the directive is simply ignored.
When linking on OpenVMS AXP systems, you must specify the symbol tables to link against in the LINK command itself. While you can continue to use the .LINK directive for code assembled on a VAX, it is preferable to also specify the symbol table files in the VAX LINK command.
.IF NOT_DEFINED ALPHA ; Only on a VAX can we do this .LINK "SYS$SYSTEM:NETDEF.STB" ; RCB and XWB symbols .LINK "SYS$SYSTEM:SYS.STB"/SELECTIVE_SEARCH ; VMS symbols .ENDC
The best way to ensure that the needed object modules and symbol tables are referenced is to create an options file that contains the names of the files to use. For example, for a program named TEST.MAR, you might create an options file named TEST.OPT that includes these lines:
SYS$SYSTEM:NETDEF.STB !RCB and XWB symbols SYS$SYSTEM:SYS.STB/SELECTIVE_SEARCH !VMS executive symbols
You could then link the program on either OpenVMS VAX or OpenVMS AXP using the following LINK command:
$ link test.obj,test.opt/options
In fact, the TEST.OBJ name could also have been included in the .OPT file.
On Alpha AXP, all the machine instructions are of the same length, 32 bits, and are guaranteed to be naturally aligned on a longword boundary. However, VAX instructions have varying lengths and can start or end on any byte boundary. To enhance the performance of VAX code, a programmer can force the target of a branch instruction to be aligned on a longword boundary by include the .ALIGN LONG directive. (Note that this directive should only be used for branch targets that do not have a “fall-through” path!)
On AXP, the .ALIGN directive is ignored by the compiler. The ALIGN_CODE macro below can be substituted for .ALIGN LONG directives in MACRO-32 code psects.
; ; Define the ALIGN_CODE macro to longword align on VAXen. ; .MACRO ALIGN_CODE ; Define code ALIGNMENT macro .IF NOT_DEFINED ALPHA ; If we be on a VAX machine .Align Long ; Force 32-bit alignment .ENDC ; If on a VAX .ENDM ALIGN_CODE ; End ALIGN_CODE definition
Other MACRO-32 Directives
The AXP MACRO-32 compiler will issue informational, warning, and error messages when it detects coding problems in MACRO-32 source files. While all the error and warning messages indicate areas that need to be recoded, many of the informational messages can be ignored after reviewing the code that generated them. In particular, messages informing you about unaligned memory references to VMS data structures, possible unaligned quadword memory references, branches between routines, unaligned stack references, and run time stack differences may be acceptable. (The messages: %AMAC-I-BRANCHBET, %AMAC-I-MEMREFNOT, %AMAC-I-QUADMEMREF, %AMAC-I-RUNTIMSTK, %AMAC-I-UNALMEMSTO, and %AMAC-I-UNALSTKREF).
Once you have examined the code fragments that generate these messages and have determined the code is OK, you can tell the compiler to ignore these cases by issuing a .DSABL FLAGGING directive preceding the statement that generates the messages. You can re-enable the compiler messages via a .ENABL FLAGGING directive following the culprit code.
You can unconditionally turn off some or all messages for a module’s compilation by specifying the /DISABLE=FLAGGING, /NOFLAG, and/or /NOWARN qualifiers to the MACRO-32 compiler. However, since there are cases where it is advantageous to modify the code fragment the compiler complains about, we do not recommend this approach.
The following two macros can be used to prevent messages after reviewing the code.
; ; Macro to turn off/on warning messages on AXP compiler where we know ; the code is correct. ; .MACRO WARNOFF .IF DEFINED ALPHA ; Only does something on AXP .DSABL FLAGGING ; Stop the warning messages .ENDC ; .IF DEFINED ALPHA .ENDM WARNOFF .MACRO WARNON .IF DEFINED ALPHA ; Only applicable on AXP boxes .ENABLE FLAGGING ; Re-enable warning messages .ENDC ; .IF DEFINED ALPHA .ENDM WARNON
It is a common occurrence in MACRO-32 code to save a register’s contents on the stack and subsequently restore the saved value. On a VAX, this save is usually accomplished via a: PUSHL; MOVL Rx, -(SP); or a PUSHR instruction, while the restore uses a: POPL; MOVL (SP)+, Rx; or POPR. On an AXP machine, with 64-bit registers, a MOVQ instruction could be used to accomplish the save and restore. Unfortunately, this requires that the stack be quadword aligned, and as there is no way to guarantee the code is executing with a quadword aligned stack, STARLET.MLB contains two macros that use 32-bit operations to move 64-bits to the stack. (Performing a 64-bit move to an unaligned quadword address results in a ********).
To ensure that the registers are properly pushed onto the stack regardless of the platform, you might wish to use macros like PUSHREG and POPREG, as shown here:
; ; Define macros for saving and restoring registers independent of platform ; ; PUSHREG reg ; Saves the register specified in the REG argument on the stack. ; .MACRO PUSHREG reg .IF DEFINED ALPHA ; AXP architecture $PUSH64 reg ; AXP - use $PUSH64 macro .IF_FALSE ; VAX platform PUSHL reg ; Simple quick PUSHL instruction .ENDC ; End architectural differences .ENDM PUSHREG ; ; POPREG reg ; Restores previously saved register from the stack ; .MACRO POPREG reg .IF DEFINED ALPHA ; For AXP platforms $POP64 reg ; Use the macro .IF_FALSE ; VAX platforms POPL reg ; Simple POPL pseudo-opcode .ENDC ; End architectural differences .ENDM POPREG ; End of macro definition
In the last installment in this series, _DSJ_ January/February 1994 issue, we discussed ways to ensure that an address that appeared to be a system data structure address was valid. The following macro expands upon that concept and works on OpenVMS VAX 6.0+, OpenVMS VAX pre-6.0, and OpenVMS AXP systems.
; ; CHECK_TYPE macro ; Verify that a given address is a S0 data structure of a specified type. ; ; Parameters: ; ERR address to branch to if checks fail - required ; OK address to branch to if checks succeed - optional, defaults ; to next instruction following macro invocation. ; REG register containing address of data structure. Default is R5. ; TYPE data structure type code, defaults to UCB. ; OFFSET offset into data structure where type code is stored, defaults ; to UCB$B_TYPE (works w/ most VMS data structures). ; .MACRO CHECK_TYPE ERR, OK, REG=R5, TYPE=DYN$C_UCB, OFFSET=UCB$B_TYPE, ?L1 .IF BLANK, .ERROR 0 ; Missing error branch location .ENDC .IF DEFINED ALPHA ; If an AXP machine CMPL G^MMG$GL_FRESVA, REG ; Maximum system address+1 .IF_FALSE ; If not for AXP platform .IF DEFINED DPT$M_XPAMOD ; An OpenVMS VAX V6.0 system? CMPL G^MMG$GL_FRESVA, REG ; Maximum system address+1 .IF_FALSE ; Else pre-6.0 system CMPL G^MMG$GL_MAXSYSVA, REG ; Valid S0 address? .ENDC ; OpenVMS V6.0 .ENDC ; VAX vs. AXP conditional code BLSSU ERR ; Invalid address - don't use it CMPB #TYPE, OFFSET(REG) ; Check the type fields BNEQ ERR ; Scram if not the desired type ; ; Throw in a few more checks if it is a UCB ; .IF IDENTICAL,,<dyn$c_ucb> CMPL #-1, UCB$L_LINK(REG) ; Deleted or deleting as we check? .IF BLANK, BGTR L1 ; Let compiler compute the address .IF_FALSE ; Use the user supplied address BGTR OK ; No ok to continue then .ENDC CMPL #0, UCB$L_LINK(REG) ; Last UCB in device chain? .IF BLANK, BEQL L1 .IF_FALSE ; Use the user supplied address BEQL OK ; Yes - allow it .ENDC BRB ERR ; No - must be invalid address .ENDC .IF BLANK, L1: .ENDC .ENDM CHECK_TYPE </dyn$c_ucb>
Sample code that uses CHECK_TYPE macro:
; ; Let us make sure we have a UCB! ; 200$: TSTL R2 ; Possible S0 address? BGEQ 400$ ; No - don't even think about it! CHECK_TYPE err=400$, ok=600$, reg=R2, type=DYN$C_UCB ; Validate R2 contains a UCB address 400$: MOVL #SS$_NODATA, R0 ; Set status code RSB ; Return to caller ALIGN ; Longword align code on VAXen 600$: MOVL UCB$L_PID(R2), R9 ; Get PID in from UCB
Despite Digital’s advice that programmers stop using assembler, and since Digital is content to let BLISS die, MACRO-32 remains the only viable solution for most systems programmers writing code for both VAX and AXP. With some careful thought and a library of macros, it is possible to keep a single MACRO-32 source file that can be used on both the VAX and the AXP, just as conditional assembly blocks have been used to detect version-dependent code between major releases of VAX/VMS.
The macros presented here can be included in a macro library by placing them in a file and executing a command like the following:
$ library/macro/create porting-macros porting-macros.mar
MACRO-32 programs can then include that library using the .LIBRARY directive:
If you come up with other useful macros like the ones presented here, please feel free to send them to us at the addresses below and we’ll mention them in a future article.
Hunter Goatley, Western Kentucky University, Bowling Green, KY.
Edward A. Heinrich, Vice-President, LOKI Group, Inc., Sandy, UT.