HP OpenVMS Systems Documentation

Content starts here

OpenVMS MACRO-32 Porting and User's Guide


Previous Contents Index

4.2.2 Changing the Compiler's Branch Prediction

The compiler provides two directives, .BRANCH_LIKELY and .BRANCH_UNLIKELY, to change its assumptions about branch prediction. The directive .BRANCH_LIKELY is for use with forward conditional branches when the probability of the branch is large, say 75 percent or more. The directive .BRANCH_UNLIKELY is for use with backward conditional branches when the probability of the branch is less than 25 percent.

These directives should only be used in performance-sensitive code. Furthermore, you should be more cautious when adding .BRANCH_UNLIKELY, because it introduces an additional branch indirection for the case when the branch is actually taken. That is, the branch is changed to a forward branch to a branch instruction, which in turn branches to the original branch target.

There is no directive to tell the compiler not to follow an unconditional branch. However, if you want the compiler to generate code that does not follow the branch, you can change the unconditional branch to be a conditional branch that you know will always be taken. For example, if you know that in the current code section R3 always contains the address of a data structure, you could change a BRB instruction to a TSTL R3 followed by a BNEQ instruction. This branch will always be taken, but the compiler will fall through and continue code generation with the next instruction. This will always cause a mispredicted branch when executed, but may be useful in some situations.

4.2.3 How to Use .BRANCH_LIKELY

If your code has forward conditional branches that you know will most likely be taken, you can instruct the compiler to generate code using that assumption by inserting the directive .BRANCH_LIKELY immediately before the branch instruction. For example:


MOVL    (R0),R1          ; Get structure
.BRANCH_LIKELY
BNEQ    10$              ; Structure exists
  .
 (Code to deal with missing structure, which is too large for
  the compiler to automatically change the branch prediction)
  .
10$:

The compiler will follow the branch and will modify the code flow as described in the previous example, moving all the code that deals with the missing structure out of line to the end of the module.

4.2.4 How to Use .BRANCH_UNLIKELY

If your code has backward conditional branches which you know will most likely not be taken, you can instruct the compiler to generate code using that assumption by inserting the directive .BRANCH_UNLIKELY immediately before the branch instruction. For example:


        MOVL    #QUEUE,R0               ;Get queue header
10$:    MOVL    (R0),R0                 ;Get entry from queue
        BEQL    20$                     ;Forward branch assumed unlikely
        .                               ;by default
        .                               ;Process queue entry
        .
        TSTL    (R0)                    ;More than one entry (known to be
        .BRANCH_UNLIKELY                ;unlikely)
        BNEQ    10$                     ;This branch made into forward
20$:                                    ;conditional branch

The .BRANCH_UNLIKELY directive is used here because the Alpha hardware would predict a backward branch to 10$ as likely to be taken. The programmer knows it is a rare case, so the directive is used to change the branch to a forward branch, which is predicted not taken.

There is an unconditional branch instruction at the forward branch destination which branches back to the original destination. Again, this code fragment is moved to a point beyond the normal routine exit point. The code that would be generated by the previous VAX MACRO code follows:


        LDQ     R0, 48(R27)             ;Get address of QUEUE from linkage sect.
10$:    LDL     R0, (R0)                ;Get entry from QUEUE
        BEQ     R0, 20$
        .
        .                               ;Process queue entry
        .
        LDL     R22, (R0)               ;Load temporary register with (R0)
        BNE     R22,$L1                 ;Conditional forward branch predicted
20$:                                    ;not taken by Alpha hardware
        .
        .
        .
        (routine exit)

$L1:    BR      10$                     ;Branch to original destination

4.2.5 Forward Jumps into Loops

Because of the way that the compiler follows the code flow, a particular case that may not compile well is a forward unconditional branch into a loop. The code generated for this case usually splits the loop into two widely separated pieces. For example, consider the following macro coding construct:


        (Allocate a data block and set up initial pointer)
        BRB     20$
10$:    (Move block pointer to next section to be moved)

20$:    (Move block of data)
        (Test - is there more to move?)
        (Yes, branch to 10$)

        (Remainder of routine)

The macro compiler will follow the BRB instruction when generating the code flow and will then fall through the subsequent conditional branch to 10$. However, because the code at 10$ was skipped over by the BRB instruction, it will not be generated until after the end of the routine. This will convert the conditional branch into a forward branch instead of a backward branch. The generated code layout will look like the following:


        (Allocate a data block and set up initial pointer)
20$:    (Move block of data)
        (Test - is there more to move?)
        (Yes, branch to 10$)
        .
        .
        (Remainder of routine)
        (Routine exit)
        .
        .
10$:    (Move block pointer to next section to be moved)
        BRB     20$

This results in the loop being very slow because the branch to 10$ is always predicted not taken, and the code flow has to keep going back and forth between the two locations. This situation can be fixed by inserting a .BRANCH_LIKELY directive before the conditional branch back to 10$. This will result in the following code flow:


        (Allocate a data block and set up initial pointer)
20$:    (Move block of data)
        (Test - is there more to move?)
 (No, branch to $L1)
10$:    (Move block pointer to next section to be moved)
        BRB     20$
$L1:
        (Remainder of routine)

4.3 Code Optimization

The MACRO-32 compiler performs several optimizations on the generated code. It performs all of them by default except VAXREGS. You can change these default values with the /OPTIMIZE switch on the command line. The valid options are:

  • ADDRESSES
    The compiler recognizes that the same address is referenced multiple times, and only loads the address once for use by multiple references.

  • REFERENCES
    The compiler recognizes that the same data value is referenced multiple times, and only loads the data once for use by multiple references, subject to restrictions to ensure that the data being used is not stale.
  • PEEPHOLE
    The compiler identifies instruction sequences that can be identically performed by smaller instruction sequences, and replaces the longer sequences with the shorter ones.
  • SCHEDULING
    The compiler uses its knowledge of the nature of the multiple instruction issue ability of the Alpha architecture to reschedule the code for optimum performance.
  • VAXREGS
    By default, the registers from R13 through R28 may be used as temporary scratch registers by the compiler if they are not used in the source code. When VAXREGS is specified, the compiler may also use any of the VAX register set (R0 through R12) that are not explicitly used by the MACRO source code. VAX registers used in this way will be restored to their original values at routine exit unless declared SCRATCH.

Note

Debugging is simplified if you specify /NOOPTIMIZE, because the optimizations include relocating and rescheduling code. For more information, see Section 2.12.1.

4.3.1 Using the VAXREGS Optimization

To use the VAXREGS optimization, you must ensure that all routines correctly declare their register usage in their .CALL_ENTRY, .JSB_ENTRY, or .JSB32_ENTRY routine declarations. In addition, you must identify any VAX registers that are required or modified by any routines that are called. By default, the compiler assumes that no VAX registers are required as input to any called routine, and that all VAX registers except R0 and R1 are preserved across the call. To declare this usage, use the READ and WRITTEN qualifiers to the compiler directive .SET_REGISTERS. For example:


.SET_REGISTERS  READ=<R3,R4>, WRITTEN=R5
JSB     DO_SOMETHING_USEFUL

In this example, the compiler will assume that R3 and R4 are required inputs to the routine DO_SOMETHING_USEFUL, and that R5 is overwritten by the routine. The register usage can be determined by using the input mask of DO_SOMETHING_USEFUL as the READ qualifier, and the combined output and scratch masks as the WRITE qualifier.

Note

Using the VAXREGS qualifier without correct register declaration for both routine entry points and routine calls will produce incorrect code.

4.4 Common-Based Referencing

On an Alpha system, references to data cells generally require two memory references---one reference to load the data cell address from the linkage section and another reference to the data cell itself. If several data cells are located in proximity to one other, and the ADDRESSES optimization is used, the compiler can load a register with a common base address and then reference the individual data cells as offsets from that base address. This eliminates the load of each individual data cell address and is known as common-based referencing.

The compiler performs this optimization automatically for local data psects when the ADDRESSES optimization is turned on. The compiler generates symbols of the form $PSECT_BASEn to use as the base of a local psect.

To use common-based referencing for external data psects, you must create a prefix file which defines symbols as offsets from a common base. The prefix file cannot be used when assembling the module for OpenVMS VAX because the VAX MACRO assembler does not allow symbols to be defined as offsets from external symbols.

4.4.1 Creating a Prefix File for Common-Based Referencing

The following example illustrates the benefits of creating a prefix file to use common-based referencing. It shows:

  • Code generated without the use of a prefix file
  • How to create a prefix file
  • Code generated with the use of a prefix file

Consider the following simple code section (CODE.MAR), which refers to data cells in another module (DATA.MAR):


Module DATA.MAR:

        .PSECT  DATA    NOEXE
BASE::
A::     .LONG   1
B::     .LONG   2
C::     .LONG   3
D::     .LONG   4
        .END

Module CODE.MAR:

        .PSECT CODE     NOWRT

E::     .CALL_ENTRY
        MOVL    A,R1
        MOVL    B,R2
        MOVL    C,R3
        MOVL    D,R4
        RET
        .END

When compiling CODE.MAR without using common-based referencing, the following code is generated:

In the linkage section:


        .ADDRESS        A
        .ADDRESS        B
        .ADDRESS        C
        .ADDRESS        D

In the code section (not including the prologue/epilogue code):


        LDQ     R28, 40(R27)            ;Load address of A from linkage section
        LDQ     R26, 48(R27)            ;Load address of B from linkage section
        LDQ     R25, 56(R27)            ;Load address of C from linkage section
        LDQ     R24, 64(R27)            ;Load address of D from linkage section
        LDL     R1, (R28)               ;Load value of A
        LDL     R2, (R26)               ;Load value of B
        LDL     R3, (R25)               ;Load value of C
        LDL     R4, (R24)               ;Load value of D

By creating a prefix file that defines external data cells as offsets from a common base address, you can cause the compiler to use common-based referencing for external references. A prefix file for this example, which defines A, B, C, and D in terms of BASE, follows:


A = BASE+0
B = BASE+4
C = BASE+8
D = BASE+12

When compiling CODE.MAR using this prefix file and the ADDRESSES optimization, the following code is generated:

In the linkage section:


.ADDRESS        BASE            ;Base of data psect

In the code section (not including the prologue/epilogue code):


LDQ     R16, 40(R27)            ;Load address of BASE from linkage section
LDL     R1, (R16)               ;Load value of A
LDL     R2, 4(R16)              ;Load value of B
LDL     R3, 8(R16)              ;Load value of C
LDL     R4, 12(R16)             ;Load value of D

In this example, common-based referencing shrinks the size of both the code and the linkage sections and eliminates three memory references. This method of creating a prefix file to enable common-based referencing of external data cells can be useful if you have one large, separate module that defines a data area used by many modules.


Chapter 5
MACRO-32 Programming Support for 64-Bit Addressing

This chapter describes the 64-bit addressing support provided by the MACRO-32 compiler and associated components. The changes are primarily for argument passing and receiving and for address computations.

5.1 Guidelines for 64-Bit Addressing

The following guidelines pertain to using 64-bit addressing in VAX MACRO code that is compiled for OpenVMS Alpha:

  • Limit its use to code that you have ported to OpenVMS Alpha.
    For any new development on OpenVMS Alpha, Compaq recommends the use of higher-level languages.
  • Make 64-bit addressing explicit in your code.
    The 64-bit addressing qualifiers, macros, directives, and built-ins produce code that is more reliable and easier to maintain.

5.2 New and Changed Components for 64-Bit Addressing

The new and changed components that provide MACRO-32 programming support for 64-bit addressing are shown in Table 5-1.

Table 5-1 New and Changed Components for 64-Bit Addressing
Component Description
$SETUP_CALL64 New macro that initializes the call sequence.
$PUSH_ARG64 New macro that does the equivalent of argument pushes.
$CALL64 New macro that invokes the target routine.
$IS_32BITS New macro for checking the sign extension of the low 32 bits of a 64-bit value.
$IS_DESC64 New macro for determining if descriptor is a 64-bit format descriptor.
QUAD=NO/YES New parameter for page macros to support 64-bit virtual addresses.
/ENABLE=QUADWORD The QUADWORD parameter was extended to include 64-bit address computations.
.CALL_ENTRY QUAD_ARGS=TRUE|FALSE QUAD_ARGS=TRUE|FALSE is a new parameter that indicates the presence (or absence) of quadword references to the argument list.
.ENABLE QUADWORD/.DISABLE QUADWORD The QUADWORD parameter was extended to include 64-bit address computations.
EVAX_SEXTL New built-in for sign extending the low 32 bits of a 64-bit value into a destination.
EVAX_CALLG_64 New built-in to support 64-bit calls with variable-size argument lists.
$RAB64 and $RAB64_STORE New RMS macros for using buffers in 64-bit address space.

5.3 Passing 64-Bit Values

The method that you use for passing 64-bit values depends on whether the size of the argument list is fixed or variable. These methods are described in the following sections.

5.3.1 Calls with a Fixed-Size Argument List

For calls with a fixed-size argument list, use the new macros shown in Table 5-2.

Table 5-2 Passing 64-Bit Values with a Fixed-Size Argument List
Step Use...
1. Initialize the call sequence $SETUP_CALL64
2. "Push" the call arguments $PUSH_ARG64
3. Invoke the target routine $CALL64

An example of using these macros follows. Note that the arguments are pushed in reverse order, which is the same way a 32-bit PUSHL instruction is used.


MOVL           8(AP), R5         ; fetch a longword to be passed
$SETUP_CALL64  3                 ; Specify three arguments in call
$PUSH_ARG64    8(R0)             ; Push argument #3
$PUSH_ARG64    R5                ; Push argument #2
$PUSH_ARG64    #8                ; Push argument #1
$CALL64        some_routine      ; Call the routine

The $SETUP_CALL64 macro initializes the state for a 64-bit call. It is required before $PUSH_ARG64 or $CALL64 can be used. If the number of arguments is greater than six, this macro creates a local JSB routine, which is invoked to perform the call. Otherwise, the argument loads and call are inline and very efficient. Note that the argument count specified in the $SETUP_CALL64 does not include a pound sign (#). (The standard call sequence requires octaword alignment of the stack with its arguments at the top. The JSB routine facilitates this alignment.)

The inline option can be used to force a call with greater than six arguments to be done without a local JSB routine. However, there are restrictions on its use (see Appendix E).

The $PUSH_ARG64 macro moves the argument directly to the correct argument register or stack location. It is not actually a stack push, but it is the analog of the PUSHL instructions used in a 32-bit call.

The $CALL64 macro sets up the argument count register and invokes the target routine. If a JSB routine was created, it ends the routine. It reports an error if the number of arguments pushed does not match the count specified in $SETUP_CALL64. Both $CALL64 and $PUSH_ARG64 check that $SETUP_CALL64 has been invoked prior to their use.

5.3.1.1 Usage Notes for $SETUP_CALL64, $PUSH_ARG64, and $CALL64

Keep these points in mind when using $SETUP_CALL64, $PUSH_ARG64, and $CALL64:

  • The arguments are read as aligned quadwords. To pass a longword from memory, move it to a register first, and then use that register in $PUSH_ARG64, as shown in the example in Section 5.3.1. Similarly, if you know the quadword you want to pass is unaligned, move the value to a register first. Also, keep in mind that indexed operands, such as (R4)[R0], will be evaluated using quadword indexing when used in $PUSH_ARG64.
  • If the number of arguments is greater than six, so that a local JSB routine is created, no SP or AP references are allowed between the $SETUP_CALL64 and $CALL64. The $PUSH_ARG64 and $CALL64 macros do report uses of these registers in operands, but they are not allowed in other instructions in this range and cannot be flagged. To pass an AP- or SP-based argument in this case, move it to a register before the $SETUP_CALL64 invocation.
  • If the number of arguments is greater than six, do not rely on values in registers above R15 surviving the $SETUP_CALL64 invocation. Use a nonscratch register as a temporary register instead. For example, suppose you want to pass a value from a stack location, and the call has more than six arguments. In this case, you need to move the value to a register. Rather than using a scratch register such as R28, use a VAX register, such as R0. If all the VAX registers are in use, use R13, R14, or R15.
  • It is safe to use the scratch registers above R16 within the range between the $SETUP_CALL64 and the $CALL64. However, you must be careful not to use an argument register that has already been loaded. The argument registers are loaded in downward order, from R21 through R16. So, suppose a call passes six arguments. It is not safe to use R21 after the first $PUSH_ARG64, because that has loaded R21. The $PUSH_ARG64 macro checks for operands that refer to argument registers that have already been loaded. If any are found, the compiler reports a warning. The safest approach is to use registers R22 through R28 when a temporary register is required.

Note

The $SETUP_CALL64, $PUSH_ARG64, and $CALL64 macros are intended to be used in an inline sequence. That is, you cannot branch into the middle of a $SETUP_CALL64/$PUSH_ARG64/$CALL64 sequence, nor can you branch around $PUSH_ARG64 macros or branch out of the sequence to avoid the $CALL64.

For more information about $SETUP_CALL64, $PUSH_ARG64, and $CALL64, see Appendix E.


Previous Next Contents Index