Compiler support for one-trip do loops

I’m trying to bring an old code circa 1975 that ran on an IBM system back to life and am battling a pesky seg fault. One thing I noticed is that one of the routine called occasionally has what in the old days would be a one trip do. ie DO I=1,N will sometimes have N=0. The code appears to be designed to work this way. So how do modern compilers handle this situation. I presume the loop never gets iterated unless you set some kind of compiler flag. Since I’m too lazy to look this up for myself :grinning_face_with_smiling_eyes:, can someone list the flags to turn on one-trip do on ifx, gfortran, and nvfortran.

Thanks

In most cases, you can change the code to something like this:

i = 1
do
   ...loop code here
   i = i + 1
   if ( i > N ) exit
enddo

Yeah, but I believe this happens throughout the code so I want to try the compiler option approach first before I start looking at every do loop where this can occur. For the one loop I thought was the problem I did

nc = n
if (nc == 0) nc = 1
do i=1,nc

but that didn’t eliminate my seg fault problem

ifx has the -f66 option but only in a Linux system. I don’t know whether gfortran --std=legacy and lfortran --std=legacy would do what you want.

Do i = 1,max(n,1)

Note @JohnCampbell (correctly) does not change the value of N. Changing NC as show in the previous discussion could have unintended side effects and if attempted generically could have other issues like changing an intent(in) variable, etc. Rewriting the loop to test at the bottom as also shown or tricking DO WHILE would also work, but unless updating the code is a massive task you really want to bite the bullet and not leave that there as something that at best requires special compiler options and will be harder and harder to support in the future, but be very careful about changing “NC”. in the example loop

Also note if do i=j,k,l is used that do i=-10,-4 or do i= -10,-20,-1 would require you to think about whether min() or max() was appropriate and whether some other value than zero is needed as the fixed value in the function call.

It was unspecified prior to F77, though most compilers implemented one trip minimum. From F77 onwards, it is required to do the zero trip test prior to entering the loop.

One also has to look at the code after the loop to see if the value of the loop control variable is being used. Sometimes it is used to indicate whether the loop executed. And if so, if there was an early exit or normal completion.

(Note that lfortran currently requires the --use-loop-variable-after-loop option to get this right.)

In principle yes, but in practice the one-trip loop conventions only apply to f66 era codes. In f66, the m1, m2, and m3 loop parameters (not expressions, just constants or simple variables) all had to be positive. Also m1<m2 was required, so all standard conforming loops had to execute at least once. The one-trip convention was to cover nonconforming codes when m2≤m1. Many compilers implemented f66 loops with the initialization of the index, then the loop body, and then finally the increment and test at the end of the loop. So a natural way to mimic that behavior with modern syntax is to do exactly those operations in that order. Modern fortran (since f77 even) allows expressions for the loop parameters, so max(1,N) also achieves the same one-trip behavior for that previously nonconforming code. It should also be noted that in f77 the committee had the choice of which way to define that behavior. They could have chosen the one-trip behavior, which was the extension implemented in many compilers, or they could choose the zero-trip behavior. I think they looked at various codes to see which convention was the most useful, and they chose the latter.

It is interesting to compare this to the corresponding mathematical summation and integral range conventions, which sometimes depend on the context. That is, a summation with upper bound less than the lower bound sometimes means to sum the terms backwards, and sometimes it means to skip the summation and produce a zero result. The integral situation almost always means to integrate backwards (or reverse the range and change the overall sign), it almost never means a zero integral.

Forgot they could only be a positive ascending sequence, at least in most compilers.

Does spag or fpt or … have an option to handle replicating one-trip behavior?

If g77 is available on a platform I think it had a -onetrip option

I thought -Wzerotrip option on gfortran might at least give a warning; but could not tell from documenation if it was only for dead code detected at compile time or was a runtime check but could not get it to do anything I could detect; but pretty certain gfortran for one has no -onetrip type switch, making actually updating the code even more reasonable, as it was one of the compilers listed.

Indeed, g77 manpage states:

-fonetrip
Executable iterative “DO” loops are to be executed at least once each time they are reached.

ANSI FORTRAN 77 and more recent versions of the Fortran standard specify that the body of an iterative “DO” loop is not executed if the number of iterations calculated from the parameters of the loop is less than 1. (For example, DO 10 I = 1, 0.) Such a loop is called a zero-trip loop.

Prior to ANSI FORTRAN 77, many compilers implemented “DO” loops such that the body of a loop would be executed at least once, even if the iteration count was zero. Fortran code written assuming this behavior is said to require one-trip loops. For example, some code written to the FORTRAN 66 standard expects this behavior from its “DO” loops, although that standard did not specify this behavior.

So code relaying on one-trip feature was not portable even in F66 times.

More debugging revealed that my problem was not the zero trip do problem after all but was an issue with how the loop counter is incremented and what value it has when it exits the loop. The old code I’m working on has a large integer buffer that is used to create an old school array based “linked list”. It stores blocks of integers that hold binary encoded data (about 10 words per block) and then stores an integer index that signals the start of the next block. So the loop looked something like this:

     is = 1
100 iend = is + blksize -1  ! block size = 10
      do icnt = is,iend
          ....
      end do
      is = ibuf(icnt+1)
      go to 100

However for most modern compilers, the value of icnt after the end of the loop will be iend + 1. The value of is for the next block loop is taken from iend+2 in ibuf when it appears the original code was expecting icnt to be equal to iend instead of icnt+1. As a result, the code was grabbing a full 32 bit binary encoded number instead of the integer pointer to the next block of data (ie it was getting something like -413218 instead of 10) so this triggered a seg. fault. So my question is now, was the value that a loop counter has when it exits a loop ever specified in the standard. Also when did the current behaviour start appearing in compilers. Note this code was developed on circa 1975 IBM systems using the G compiler. Does anyone know/remember what the loop counter behavior was for that system.

Strangely my memory was right to tell me that in the ancient times, namely F66, the control variable became undefined if the DO loop exited naturally, i.e. when it surpassed the terminal value. Indeed, all three (initial <= terminal and increment) were required to be positive.

7.1.2.8.1 (ANSI X 3.9 1966 Fortran 66)
(4) […] If the value of the control variable is greater than the value represented by its associated terminal parameter, then the DO is said to have been satisfied and the control variable becomes undefined.
(6) Upon exiting from the range of a DO by the execu¬ tion of a GO TO statement or an arithmetic IF statement, that is, other than by satisfying the DO, the control variable of the DO is defined and is equal to the most recent value attained as defined in the preceding paragraphs.

That had changed in F77, when ‘iteration count’ was introduced, computed before starting the loop, zero-trip allowed, float variables/expressions, negative values. F77 states that

11.10.2 (ANSI X3J3/90.4)
When a DO-loop becomes inactive, the DO-variable of the DO-loop retains its last defined value.

So again, if code developed in 1975 relied on DO variable retaining its value (no matter if iend+1 or iend+2 after natural ending of the loop, it was not only non-portable but also non-conforming.

Of course another option is that the listing provided in the old report describing the code has a bug in it. Very possible since it was research done at a University by a grad student for a Gov’t agency. Not including the most recent listing (aka the correct one) in a report is unfortunately still standard operating procedures for some grad students.

Also mentioned in this post: Poll: refactoring a chunk of legacy code - #12 by mecej4

Per the F66 Standard, if all the iterations are performed, after the loop the control variable is ‘undefined’. However if you branch out early, it is defined with the value it had during that iteration of the loop. (Sections 7.1.2.8.1(4) and (6))

F77 cleaned all this up by explicitly defining what happens to the control variable with zero-trip, early exit, and normal exit.

/f66 is supported on Windows. f66

After the F77 standard was released, many codes continued to be developed using Fortran compilers that supported 1-trip DO, through to the early 80’s. This was the case for most “Mini” and PC usage, as these older compilers were more reliable and produced more efficient code.

However, as well as introducing zero-trip, F77 also standardised the exit value of the do subscript, which had been notoriously variable between compilers, which highlighted the grey zone between developing on different compilers.

Up to the mid 80’s, it had been important to document which compiler (ie which variant of Fortran) had been used to develop the code. No developer wrote Fortran to comply with the F66 standard, as no available compiler fully complied.
It was not until the F90/95 standard that complying to the standard became generally considered. Lahey, Salford and Microsoft/HP/Compaq/Dec/Intel all continued to support many historic and essential extensions for their code base.

Thank you @sblionel. I had just used man ifx which said (L*X only) so I must never trust that again but instead go to the full manual on the particular feature I wanted to know about.