Modernising an old Fortran program

Posted on
7 Sep 2017

One of the delights of turning 70 is that skills learned many years ago become in demand. Not too many people these days learn about ‘ALTERNATE RETURNS’ and ‘COMMON BLOCKS’ even if they are taught Fortran. However in order to ‘modernise’ an old program one has to know exactly what these constructs did.

Recently I agreed to help a friend with a program he would like to improve. The alternate return was relatively easy:

An old routine might read:

PROGRAM MAIN
      REAL A
      EXTERNAL MYSQRT
      
      READ(5,800) A
 800  FORMAT(1E20.10)
      CALL MYSQRT(A,*100,*200)
      WRITE(6,900) A
      GOTO 700
 900  FORMAT (19HTHE SQUARE ROOT IS ,1E20.5)
 100  WRITE(6,901)
 901  FORMAT(14H A IS NEGATIVE)
      GOTO 700
 200  WRITE(6,902)
 902  FORMAT(17H A IS LESS THAN 1)
 700  CONTINUE
      END

It might be called from a program thus:

      SUBROUTINE MYSQRT(A,*,*)
      REAL A
      IF (A.LT.0.0) RETURN 1
      IF (A.LT.1.0) RETURN 2
      A = SQRT(A)
      RETURN
      END

The alternate return is the use of labels as the actual arguments to the call of MYSQRT and the use of RETURN 1 and RETURN 2  in MYSQRT to jump to the label in the calling subprogram specified by the corresponding argument.

The construct is an ‘Obsolescent Feature’ of modern Fortran so it seems sensible to simply set an integer parameter and test this on exit. This is easy to deduce once you know about the old construct.

I found it more challenging when faced with COMMON BLOCKS. These are ubiquitous in older programs and were a means of communicating information between routines, other than via the routine parameter lists. They might also have been used as a way of re-using scarce memory space.

I thought I knew about them and cast my mind back to days of yore:

BLANK COMMON did not have a name associated with it and was written:

       REAL(100) X,Y
       COMMON X, Y 

So a main program might read:

      PROGRAM DKS
      INTEGER I
      REAL X(5),Y(5)
      COMMON X,Y
      EXTERNAL MYSUB
      
      DO 10 I=1,5
       X(I) = FLOAT(I)+ 0.5
       Y(I) = X(I)**2
 10   CONTINUE
      WRITE(6,900) Y
 900  FORMAT(10H VALUES = , 5F20.5)
      CALL MYSUB()
      END

And a subroutine may utilise the fact that a REAL and INTEGER were defined to occupy the same storage space thus:

      SUBROUTINE MYSUB()
      INTEGER IY(6),I
      REAL Z(4)
      COMMON Z,IY
      WRITE(6,900) Z
 900  FORMAT(10H VALUES = , 4F20.5)
      DO 10 I=1,6
        IY(I) = I
 10   CONTINUE
      WRITE(6,901) IY
 901  FORMAT(10H VALUES = , 6I10)  
      RETURN
      END

Please note that values were communicated using storage association i.e. the value of Z(4) was obtained from blank common as the fourth storage-unit location from the start of blank common viz X(4). It is clear that such a mechanism is powerful, but error prone.

This has laid the groundwork for a discussion of labelled common. The very artificial program below illustrates its use:

      PROGRAM DKSPROG
      REAL X(5),Y(5)
      COMMON /DKS/ X
      COMMON /DKS/ Y
C     EXTENDS THE LABELLED COMMON BLOCK - 10 BASIC STORAGE UNITS
      EXTERNAL MYSUB1, MYSUB2
 
      CALL MYSUB1()
      CALL MYSUB2()
      END
      
      SUBROUTINE MYSUB1()
      INTEGER IY(6)
      REAL Z(4)
      COMMON /DKS/ Z,IY
c     STILL 10 STORAGE UNITS, BUT A DIFFERENT SPLIT      
      WRITE(6,900) Z
 900  FORMAT(10H VALUES = , 4F20.5)

      RETURN
      END
      
      SUBROUTINE MYSUB2()
      INTEGER IY(6),I
      REAL Z(4)
      COMMON /DKS/ Z, IY
      DO 10 I=1,6
        IY(I) = I
 10   CONTINUE
      WRITE(6,901) IY
 901  FORMAT(10H VALUES = , 6I10) 
      RETURN
      END
      
      BLOCK DATA
C     CAN INITIALISE LABELLED COMMON      
      REAL X(5),Y(5)
      COMMON /DKS/ X,Y
      DATA X/1.5,2.5,3.5,4.5,5.5/
      DATA Y/10.0,20.0,30.0,40.0,50.0/
      END

The points to note here are that the common block has been given a name DKS and that several common blocks might occur in a program unit. If they are given the same name then they are defined to extend the common block. :Labelled common may be initialised in a BLOCK DATA program unit.

In the days when COMMON blocks prevailed, memory was scarce and large programs could not be held in memory and so OVERLAYING was prevalent, when pieces of program were swapped in an out of memory. These determined some of the rules governing when common block information was retained between routine calls. Early programs may well have been written before standardisation was embraced, so some variation might be anticipated. In general initialisation via BLOCK DATA was sufficient to ensure an implicit SAVE, though this might be made explicit with a command such as:

SAVE /DKS/

In this example the common block appeared in the main program and would be retained whilst this were running, but had it been absent from the main program and only appeared in routines mysub1 and mysub2 then, lacking an explicit SAVE, information might not be preserved between the call of mysub1 and mysub2. In practice compilers may do this now, but they are not constrained to do so. It makes sense to bear this in mind.

If we think loosely that modern Fortran has MODULES as the more general and safer alternative to COMMON BLOCKS then we might wonder what the rules are for the retention of values within the modules. Originally they mirrored the common block rules, but the answer now is both welcome and surprising. ( I am grateful to my colleague Malcolm Cohen, for clarifying this for me.):

“In Fortran 2008, everyone agreed that it was pointless to allow this state of affairs, and we made module variables “default SAVEd”, i.e. they never went away.  Allocatable variables remain allocated unless explicitly deallocated by the user.”

(Malcolm has been Project Editor for the ISO/IEC Fortran standard since 2005 hence the “we”. He is the principal author of the NAG Fortran Compiler and together with NAG Member, John Reid and Michael Metcalf has published a series of books on the Fortran language.)

Armed with this knowledge and a Fortran 2008-compliant compiler we might then translate the old program above to :

    Module myvars
      Real, Allocatable :: x(:)
      Integer, Allocatable :: iy(:)
    End Module

    Module myrouts
    Contains
      Subroutine mysub1()
        Use myvars, Only: x

        Allocate (x(5))
        x = [ 1.5, 2.5, 3.5, 4.5, 5.5 ] ! can assign values
        Print *, x
        x = x(1:4)                 ! can shrink array size
        Return
      End Subroutine

      Subroutine mysub2()
        Use myvars, Only: x, iy
        Integer :: i

        Allocate (iy(6))
        Do i = 1, 6
          iy(i) = i
        End Do
        Print *, 'x-values = ', x
        Print *, 'iy-Values = ', iy
        Deallocate (iy)            ! Can free up memory as soon as it has been
                                   ! used
        Return
      End Subroutine
    End Module

    Program newdksprog
      Use myrouts, Only: mysub1, mysub2
      ! Note module myvars not mentioned.
      Implicit None

      Call mysub1()
      Call mysub2()
      Stop 'ok'
    End Program

You will notice that the module myvars does not appear in the main program, yet I can be sure, because I am using the 2008-compliant NAG Compiler, that the module variables are preserved between calls of mysub1 and mysub2.


Of course real life is very different from the idealised views presented in this article. In practice developers will have been altering and adding to an original program over the years. It is perfectly natural that, as new Fortran facilities are introduced and compilers evolve to cater for them that programmers use those features that they think would be useful to them. This can lead to some difficulties, as the Fortran standards committees have been careful not to cause existing programs to fail by removing existing features, however undesirable, from the language. They have however been careful not to extend them.

The old EQUIVALENCE statement illustrates this. An

EQUIVALENCE

statement specifies that two or more variables or arrays in a program unit share the same memory. Its use certainly inhibited compiler optimisation and so its use has always been kept to a minimum within NAG code, but developers somewhat set in their ways might have used it to save on memory, effectively using it as a means of sharing memory between different types of arrays, for example. In a world where we have allocatable arrays within the Fortran language this is often unnecessary. Also of course Fortran types have been enriched. The present standard does not allow equivalence to be used between those newer types and the original basic types.