Chapter 6. Using Directives and Assertions

This chapter contains the following sections:

Overview of Directives

The optimizations described in Chapter 4, “Controlling Scalar Optimization,” and in Chapter 5, “Inlining and Interprocedural Analysis” are controlled by driver options. However, you can use specially-formatted comment lines called directives and assertions to control these optimizations over small units of program code. In this way, you can save time by having the compiler apply optimizations to only the segments of code that can benefit (such as deeply nested loops), or you can prevent the compiler from modifying code that should be left unchanged.

A directive is a statement that tells the compiler to treat the following source text in a particular way, for instance, by applying or not applying inlining to it. An assertion is a statement that tells the compiler something about the following source text, for example that it does or does not use equivalenced identifiers to access the same memory location. Both directives and assertions are written as specially-formatted comment lines. Because they are syntactically comments, directives and assertions are automatically ignored by any compiler that does not support them.


Note: In free-format source, a directive or assertion begins with the two characters “!$.” In fixed-format source, a directive or assertions begins with “C$.” In either case, the directive must begin in the first column of the source line in order to be recognized.


Recognizing Directives and Assertions

By default, the compiler recognizes all Silicon Graphics directives and assertions. You can use the –WK,–directives driver option to selectively enable/disable certain directives and assertions.

The –directives=list option (or –dr=list) specifies which type of directives to accept. list can contain any combination of the following letters:

a

Accept Silicon Graphics C*$* ASSERT assertions.

c

Accept Cray™ CDIR$ directives.

k

Accept Silicon Graphics C*$* and C$PAR directives.

s

Accepts directives based on the PCF (Parallel Computing Forum) X3H5 guidelines.

v

Accepts VAST™ CVD$ directives.

The default value for list is acksv. For example, –WK,–directives=k enables Silicon Graphics directives only, whereas –WK,–directives=kas enables Silicon Graphics directives and assertions and Sequent directives. To disable all of the above options, enter –nodirectives or –directives (without any values for list) on the command line.

In addition to specifying –WK,–directives=a in list, you can control whether the compiler accepts assertions using the C*$* ASSERTIONS and C*$* NOASSERTIONS directives (see “Using Assertions”).

Using Directives

Directives enable, disable, or modify a feature of the compiler. Essentially, directives are driver options specified within the input file instead of on the command line. Unlike driver options, directives have no default setting. To invoke a directive, you must either toggle it on or set a desired value for its level.

Directives and Driver Options

Directives allow you to enable, disable, or modify a feature of the compiler in addition to, or instead of, driver options. Directives placed on the first line of the input file are called global directives. The compiler interprets them as if they appeared at the top of each program unit in the file. Use global directives to ensure that the program is compiled with the correct driver options. Directives appearing anywhere else in the file apply only until the end of the current program unit. The compiler resets the value of the directive to the global value at the start of the next program unit. (Set the global value using a driver option or a global directive.)

Some driver options act like global directives. Other driver options override directives. Many directives have corresponding driver options. If you specify conflicting settings to the driver and a directive, the compiler chooses the most restrictive setting. For Boolean options, if either the directive or the driver has the option turned off, it is considered off. For options that require a numeric value, the compiler uses the minimum of the driver setting and the directive setting.

Supported Directives

Table 6-1 lists the directives supported by the compiler. In addition to the standard Silicon Graphics directives, the compiler supports the Cray and VAST directives listed in the table. The compiler maps these directives to corresponding Silicon Graphics assertions (see “Overview of Assertions” for details.)

Table 6-1. Directives Summary

Directive

Compatibility

C*$*ARCLIMIT(n)

Silicon Graphics

C*$*[NO]ASSERTIONS

Silicon Graphics

C*$* EACH_INVARIANT_IF_GROWTH(n)

Silicon Graphics

C*$* [NO]INLINE

Silicon Graphics

C*$* [NO]IPA

Silicon Graphics

C*$* MAX_INVARIANT_IF_GROWTH(n)

Silicon Graphics

C*$* OPTIMIZE(n)

Silicon Graphics

C*$* ROUNDOFF(n)

Silicon Graphics

C*$* SCALAR OPTIMIZE(n)

Silicon Graphics

C*$* UNROLL(integer[,weight])

Silicon Graphics

CDIR$ NO RECURRENCE

Cray

CVD$ [NO] DEPCHK

VAST

CVD$ [NO]LSTVAL

VAST

Keep in mind that directives begin with “C” (the comment marker) in fixed format source, and with “!” free-format source; but they always begin in column 1 in either case.

Controlling Internal Table Size

The C*$* ARCLIMIT(integer) directive sets the minimum size of the internal table that the compiler uses for data dependence analysis. The greater the value for integer, the more information the compiler can keep on complex loop nests. The maximum value and default value for integer is 5000.

When you specify this directive globally, it has the same effect as the –arclimit driver option (refer to “Controlling Internal Table Size” for details).

Setting Invariant IF Floating Limits

The C*$* EACH_INVARIANT_IF_GROWTH and the C*$* MAX_INVARIANT_IF_GROWTH directives control limits on the floating of invariant IF statements. This process generally involves duplicating the body of the loop, which can increase the amount of code considerably. Refer to “Setting Invariant IF Floating Limits” for details about invariant IF floating.

The C*$* EACH_INVARIANT_IF_GROWTH(integer) directive limits the total number of additional lines of code generated through invariant IF floating in a loop. You can control this limit globally with the –each_invariant_if_growth driver option (see “Setting Invariant IF Floating Limits”).

You can limit the maximum amount of additional code generated in a program unit through invariant IF floating with the C*$* MAX_INVARIANT_IF_GROWTH(integer) directive. Use the –max_invariant_if_growth driver option to control this limit globally (see “Setting Invariant IF Floating Limits”).

These directives are in effect until the end of the routine or until reset by a succeeding directive of the same type. Examine the loop in Example 6-1.

Example 6-1. Using Invariant-IF Directives


!*$*EACH_INVARIANT_IF_GROWTH(integer)
!*$*MAX_INVARIANT_IF_GROWTH(integer)
   DO I = ...
!*$*EACH_INVARIANT_IF_GROWTH(integer)
!*$*MAX_INVARIANT_IF_GROWTH(integer)
      DO J = ...
!*$*EACH_INVARIANT_IF_GROWTH(integer)
!*$*MAX_INVARIANT_IF_GROWTH(integer)
         DO K = ...
            section-1
            IF ( ) THEN
               section-2
            ELSE
               section-3
            ENDIF
               section-4
         ENDDO
      ENDDO
   ENDDO

In floating the invariant IF out of the loop nest, the compiler honors the constraints set by the innermost directives first. If those constraints are satisfied, the invariant IF is floated from the inner loop. The middle pair of directives is tested and the invariant IF is floated from the middle loop as long as the restrictions established by these directives are not violated. The process of floating continues as long as the directive constraints are satisfied.

Setting Optimization Level

The C*$* OPTIMIZE(integer) directive sets the optimization level in the same way as the –optimize driver option. As you increase integer, the compiler performs more optimizations, and therefore takes longer to compile. Valid values for integer are:

0

Disables optimization.

1

Performs only simple optimizations. Enables induction variable recognition.

2

Performs lifetime analysis to determine when last-value assignment of scalars is necessary.

3

Recognizes triangular loops and attempts loop interchanging to improve memory referencing. Uses special case data dependence tests. Also, recognizes special index sets called wraparound variables.

4

Generates two versions of a loop, if necessary, to break a data dependence arc.

5

Enables array expansion and loop fusion.


Setting Variations in Round Off

The C*$* ROUNDOFF(integer) directive controls the amount of variation in round off error produced by optimization in the same way as the –roundoff driver option. Valid values for integer are:

0

Suppresses any transformations that change roundoff error.

1

Alter loops only if the order of computation remains the same.

2

Allows loop interchanging around arithmetic reductions if –optimize is at least 4. For example, the floating point expression A/B/C is computed as A/(B*C).

3

Recognizes REAL (float) induction variables if –scalaropt greater than 2 or –optimize is at least 1. Enables sum reductions. Enables memory management optimizations if –scalaropt=3 (see “Performing Memory Management Transformations” for details about memory management transformations).


Controlling Scalar Optimizations

The C*$* SCALAR OPTIMIZE(integer) directive controls the amount of standard scalar optimizations that the compiler performs. Unlike the –WK,–scalaropt driver option, the C*$* SCALAR OPTIMIZE directive sets the level of loop-based optimizations (such as loop fusion) only, and not straight-code optimizations (such as dead-code elimination). Valid values for integer are:

0

Suppresses any transformations that change roundoff error.

1

Performs expression simplification (which might generate various overflow or underflow errors) for expressions with operands between binary and unary operators, expressions that are inside trigonometric intrinsic functions returning integer values, and after forward substitution. Enables strength reduction. Performs intrinsic function simplification for max and min. Enables code floating if –scalaropt is at least 1. Allows loop interchanging around serial arithmetic reductions, if –optimize is at least 4. Allows loop rerolling, if –scalaropt is at least 2.

2

Allows loop interchanging around arithmetic reductions if –optimize is at least 4. For example, the floating point expression A/B/C is computed as A/(B*C).

3

Recognizes REAL (float) induction variables if –scalaropt greater than 2 or –optimize is at least 1. Enables sum reductions. Enables memory management optimizations if –scalaropt=3 (see “Performing Memory Management Transformations” for details about memory management transformations).


Fine-Tuning Inlining and IPA

Chapter 5, “Inlining and Interprocedural Analysis,” explains how to use inlining and IPA on an entire program. You can fine-tune inlining and IPA using the C*$*[NO] INLINE and C*$*[NO] IPA directives.

The compiler ignores these directives by default. They are enabled when you specify any inlining or IPA driver option, respectively, on the command line. The –inline_manual and –ipa_manual driver options enable these directives without activating the automatic inlining/algorithms.

The C*$* [NO] INLINE directive behaves like the –inline driver option, but allows you to specify which occurrences of a routine are actually inlined. The format for this directive is

C*$*[NO]INLINE [(name[,name ... ])] [HERE|ROUTINE|GLOBAL]

The possible options are:

name

Specifies the routines to be inlined. If you do not specify a name, this directive will affect all routines in the program.

HERE

Applies the INLINE directive only to the next line; occurrences of the named routines on that next line are inlined.

ROUTINE

Inlines the named routines everywhere they appear in the current routine.

GLOBAL

Inlines the named routines throughout the source file.

If you do not specify HERE, ROUTINE, or GLOBAL, the directive applies only to the next statement.

The C*$*NOINLINE form overrides the –inline driver option and so allows you to selectively disable inlining of the named routines at specific points.

In Example 6-2, the C*$*INLINE directive inlines the first call to beta but not the second.

Example 6-2. Using Directives to Control Inlining


       do i =1,n
!*$*INLINE (beta) HERE
          call beta (i,1)
       enddo
       call beta (n, 2)

Using the specifier ROUTINE rather than HERE in the example would inline both calls. This routine must be compiled with the –inline_man driver option for the compiler to recognize the C*$* INLINE directive.

The C*$* [NO] IPA directive is the analogous directive for interprocedural analysis. The format for this directive is

C*$*[NO]IPA [(name [,name...])]  [HERE|ROUTINE|GLOBAL] 

Overview of Assertions

Assertions provide the compiler with additional information about the source program. Sometimes assertions can improve optimization results.

Assertions can be unsafe because the compiler cannot verify the accuracy of the information provided. If you specify an incorrect assertion, the compiler-generated code might produce different results than the original serial program. If you suspect unsafe assertions are causing problems, use the –WK,–nodirectives driver option or the C*$* NO ASSERTIONS directive to tell the compiler to ignore all assertions.

Table 6-2 lists the supported assertions and the scope of their effect.

Table 6-2. Assertions and Their Duration

Assertion

Scope

C*$* ASSERT [NO] ARGUMENT ALIASING

Until reset

C*$* ASSERT [NO] BOUNDS VIOLATIONS

Until reset

C*$* ASSERT [NO] EQUIVALENCE HAZARD

Until reset

C*$* ASSERT NO RECURRENCE

Next loop

C*$* ASSERT RELATION (name.xx. name)

Next loop

C*$* ASSERT [NO] TEMPORARIES FOR CONSTANT ARGUMENTS

Next loop


Using Assertions

As with a directive, the compiler treats an assertion as a global assertion if it comes before all comments and statements in the file. That is, the compiler treats the assertion as if it were repeated at the top of each program unit in the file.

Some assertions (such as C*$* ASSERT RELATION) include variable names. If you specify them as global assertions, a program uses them only when those variable names appear in COMMON blocks or are dummy argument names to the subprogram. You cannot use global assertions to make relational assertions about variables that are local to a subprogram.

Many assertions, like directives, are active until another assertion resets them or the end of the program unit or file. Other assertions are active within a program unit, regardless of where they appear in that program unit.

Certain Cray and VAST directives function like Silicon Graphics assertions. The compiler maps these directives to the corresponding assertions. These directives are described along with the related assertions later in this chapter.

There is no guarantee that a specified assertion will have an effect. The compiler notes the information provided by the assertion and uses the information if it will help.

The C*$*[NO]ASSERTIONS directive instructs the compiler to accept or ignore assertions. The C*$* NO ASSERTIONS version is in effect until the next C*$* ASSERTIONS directive or the end of the program unit.

If you specify the –directives driver option without the assertions parameter (that is, a), the compiler will ignore assertions regardless of whether or not the file contains the C*$* ASSERTIONS directive. Refer to “Recognizing Directives and Assertions” for details on the –directives driver option.

Assertions About Data Dependence

In order to perform optimizations, the compiler must analyze the dependence relations between variables. Without full information on the dependence relations, the compiler cannot safely change the source in any way that would alter the order of execution.

In many cases the compiler can infer dependences from the source text. But in some cases there is not enough information. You can add information with assertions.

Known and Assumed Dependence

When the compiler can detect a possible dependence between two variables, but cannot determine the exact nature of the dependence, it creates an assumed dependence. The loop in Example 6-3 illustrates simple forward dependence.

Example 6-3. Loop Containing Only Forward Dependence


      DO 10 i=2,n
10    X(i) = X(i) + X(i-1) * 0.5

In this example, the compiler can determine that between X(i) and X(i-1) there is a forward dependence, and that the distance is one. With this information the compiler could, for example, do loop unrolling. The loop in Example 6-4 is similar, but has an assumed dependence as well.

Example 6-4. Loop Containing Forward and Assumed Dependence


      DO 10 i=2,n
10    X(i) = X(i) + X(i-1) * X(m)

The compiler cannot be sure if there is a dependence between X(i) and X(m). If it is always true that m>n, there is no dependence, and the code in Example 6-4 can be unrolled, or parallelized, the same as the code in Example 6-3. However, if under some (or all) circumstances, 1≤μν, then for some iterations of the loop X(m) will be different than in other iterations. It is no longer safe to unroll the loop or to parallelize it.

When assumed dependence blocks an optimization, you can supply information using an assertion of the true relation, or you can assert there are no dependences.

Asserting a Relationship

The assertion C*$* ASSERT RELATION(name.xx.name) specifies the relationship between two variables or between a variable and a constant. name is the variable or constant, and xx is any of the following: GT, GE, EQ, NE, LT, or LE. This assertion applies only to the next DO statement.

The C*$* ASSERT RELATION assertion includes one or two variable names. When specified globally, this assertion will only be used when the names appear in COMMON blocks or are dummy arguments to a subprogram. You cannot use global assertions to make relational assertions about variables that are local to a subprogram.

This assertion can be used to eliminate the assumed dependence in Example 6-4. If you know that m is always greater than n (and hence there is no dependence between X(i) and X(m)), you can use the assertion as shown in Example 6-5.

Example 6-5. Asserting a Relationship


C*$* ASSERT RELATION (M .GT. N) 
      DO 10 i=2,n
10    X(i) = X(i) + X(i-1) * X(m)

With this extra information the compiler can unroll or parallelize this loop.


Note: Many relationships of this type can be cheaply tested for at run time. The compiler attempts to answer questions of this sort by generating an IF statement that explicitly tests the relationship at run time. Occasionally, the compiler needs assistance, or you might want to squeeze that last bit of performance out of some critical loop by asserting some relationship rather than repeatedly checking it at run time.


Ignoring Data Dependence Conflicts

The assertion C*$* ASSERT NO RECURRENCE(variable) tells the compiler to ignore all data dependence conflicts caused by variable in the DO loop that follows it. For example, the following code tells the compiler to ignore all dependence arcs caused by the variable X in the loop:

This assertion can be used to eliminate the assumed dependence in Example 6-4. When you know that there is no dependences on X(m), you can tell the compiler so using this assertion, as shown in Example 6-6.

Example 6-6. Loop with Dependences Denied


C*$* ASSERT NO RECURRENCE (X)
      DO 10 i=2,n
10   X(i) = X(i) + X(i-1) * X(m)

The assertion shown in Example 6-6 causes the compiler to ignore the assumed dependence, and also any other dependences involving X—including the real dependence between X(i) and X(i-1). The name M could be used instead, so as to retain information about the real dependence.

The C*$* ASSERT NO RECURRENCE assertion applies only to the next DO loop. It cannot be specified as a global assertion.

In addition to the C*$* ASSERT NO RECURRENCE assertion, the compiler supports the Cray CDIR$ NORECURRENCE assertion and the VAST CVD$ NODEPCHK directive, which perform the same function.

Assertions About Aliasing

The compiler can deal with two kinds of aliasing. An alias is a situation in which the same memory location is accessible under two or more different and apparently unrelated names.

Using Equivalenced Variables

The C*$* ASSERT [NO] EQUIVALENCE HAZARD assertion tells the compiler that your code does not use equivalenced variables to refer to the same memory location inside one loop nest. Normally, EQUIVALENCE statements allow your code to use different variable names to refer to the same storage location. The –WK,-assume=e driver option acts like the global C*$* ASSERT EQUIVALENCE HAZARD assertion (see “Controlling Global Assumptions”). The C*$* ASSERT EQUIVALENCE HAZARD assertion is active until you reset it or until the end of the program.

In Example 6-7, if arrays E and F are equivalenced, but you know that the loop does not access overlapping sections, the use of C*$* ASSERT NO EQUIVALENCE HAZARD allows the compiler to parallelize the loop.

Example 6-7. Asserting Nonequivalence


      EQUIVALENCE ( E(1), F(101) )
C*$* ASSERT NO EQUIVALENCE HAZARD
      DO 10 I = 1,N
         E(I+1) = B(I)
         C(I) = F(I)
10    CONTINUE

Under optimization, Example 6-7 is converted to Example 6-8. (The C$DOACROSS directive is covered in Chapter 7.)

Example 6-8. Result of Asserting Nonequivalence


      EQUIVALENCE (E(1), F(101))
C*$* ASSERT NO EQUIVALENCE HAZARD
C$DOACROSS SHARE(N,E,B,C,F),LOCAL(I)
      DO 10 I=1,N
         E(I+1) = B(I)
         C(I) = F(I)
10    CONTINUE

Using Argument Aliasing

The C*$* ASSERT [NO] ARGUMENT ALIASING assertion allows the compiler to make assumptions about procedure dummy arguments. It is possible to call a procedure, specifying the same variable or array element in two or more positions of the argument list. Within the procedure, two or more dummy argument names, which apparently refer to different memory locations, actually refer to the same location.

In addition, when a procedure accesses a global variable, and you pass the same variable as an argument, an alias is created. In this case, the global variable can be thought of as an implicit argument.

According to the Fortran 77 standard, you can alias a dummy variable only if you do not modify (that is, write to) the aliased variable under either name.

The subroutine in Example 6-9 violates the standard, because variable A is aliased in the subroutine (through dummy arguments C and D) and variable X is aliased (through global name X and dummy argument E).

Example 6-9. Two Kinds of Argument Aliasing


COMMON X,Y
REAL A,B
CALL SUB (A, A, X)
...
SUBROUTINE SUB(C,D,E)
COMMON X,Y
X =  ...
C =  ...
END

The driver option –assume=a acts like a global C*$* ASSERT ARGUMENT ALIASING assertion (see “Controlling Global Assumptions”). A C*$* ARGUMENT ALIASING assertion is active until it is reset or until the next routine begins.

Asserting Array Bounds Violations

The C*$* ASSERT [NO] BOUNDS VIOLATIONS assertion indicates that array subscript bounds may be violated during execution. If your program does not violate array subscript bounds, do not specify this assertion. When specified, this assertion is active until reset or until the end of the program. For formal parameters, the compiler treats a declared last dimension of (1) the same as (*).

The –WK,–assert=b driver option acts like a global C*$* ASSERT BOUNDS VIOLATIONS assertion.

In Example 6-10 the compiler assumes the first loop nest conforms to the standard, and it therefore can optimize both loops. For example, the loops can be interchanged to improve memory referencing because no A(I,J) will overwrite an A(I',J+1).

In the second nest, the assertion warns the compiler that the loop limit of the first array index (I) might violate the declared array bounds. The compiler plays it safe and optimizes only the rightmost array index.

Example 6-10. Asserting Array Bounds Safety


      DO 100 I = 1,M
         DO 100 J = 1,N
            A(I,J) = A(I,J) + B (I,J)
100   CONTINUE
C*$*ASSERT BOUNDS VIOLATIONS
      DO 200 I = 1,M
         DO 200 J = 1,N
            A(I,J) = A(I,J) + B (I,J)
200   CONTINUE


Note: The compiler always assumes that array references are within the array in order to make the rightmost index concurrentizable.

After optimization, the code of Example 6-10 is converted to the form shown in Example 6-11.

Example 6-11. Result of Asserting Array Bounds Safety


!$DOACROSS SHARE(N,M,A,B),LOCAL(J,I)
   DO J=1,N
      DO I=1,M
         A(I,J) = A(I,J) + B (I,J)
      END DO
!*$*ASSERT BOUNDS VIOLATIONS
   DO I=1,M
!$DOACROSS SHARE(N,I,A,B),LOCAL(J)
      DO J=1,N
         A(I,J) = A(I,J) + B (I,J)
      END DO
   END DO

Asserting Safety of Constant Arguments

Sometimes the compiler does not perform certain transformations when their effects on the rest of the program are unclear. For example, usually the IF-to-intrinsic transformation changes code like that in Example 6-12.

Example 6-12. Code Using Intrinsic Equivalent


SUBROUTINE X(I,N)
   IF (I .LT. N) I = N
END

The compiler recognizes the equivalence to intrinsic MAX, as in Example 6-13.

Example 6-13. IF-to-Intrinsic Conversion


SUBROUTINE X(I,N)
   I = MAX(I,N)
END

Suppose the actual parameter passed as I is a constant, such as the following,

CALL X(1,N)

In that case, it would appear that the value of the constant 1 was being reassigned. In order to avoid this possibility, without additional information the compiler does not perform transformations within the subroutine.

Most compilers automatically put constant actual arguments into temporary variables to protect against this case. The C*$*ASSERT TEMPORARIES FOR CONSTANT ARGUMENTS assertion or the –WK,–assume=c driver option (the default) informs the compiler that constant parameters are protected. The NO version directs the compiler to avoid transformations that might change the values of constant parameters.