Silverfrost Forums

Welcome to our forums

Computed GO TO triggers optimiser bug

28 Apr 2015 3:42 (Edited: 15 Jun 2015 10:15) #16245

A program with about 4,000 lines of text was producing incorrect results when compiled with /OPT. With considerable effort, I was able to pare down the code to the following reproducer.

program drvr
implicit none
integer :: i,j
do i=0,2
   call cgo(i,j)
   write(*,*)i,j
end do
end program

subroutine cgo(i,j)
implicit none
integer, intent(in) :: i
integer, intent(out) :: j
!
if (i > 0) then
	 go to (10, 20) i
end if	 
   j=0;     return
10 j=3*i-1; return
20 j=2*i+1; return
end subroutine cgo

When I compiled this using the 7.10 compiler with the /OPT option, the optimiser completely removed the IF block, and running the program gave the erroneous output

            0           0
            1           0
            2           0

instead of the correct output, which is

            0           0
            1           2
            2           5

[P.S., 15 June 2015: This bug is also present in FTN95-7.20]

28 Apr 2015 6:59 #16246

Thanks for the feedback. I have logged this for investigation.

28 Apr 2015 10:10 #16247

Someone deprecated the IF block because it contained the deprecated computed GOTO.

Political correctness in Fortran? Where will it all end?

Seriously, now I know what caused me to distrust /OPT - my code contained a single computed GOTO, legacy of an IBM1130 circa 1970. Many thanks MECEJ. Time to send it where the Holleriths and arithmetic IFs went.

I'll bet an assigned GOTO is even worse.

Eddie

28 Apr 2015 11:22 (Edited: 6 Jun 2016 6:13) #16248

The ASSIGN and Assigned GO TO statements were 'Deleted features' in F95. The Computed GO TO statement, however, was merely 'Obsolescent' in F95, and as far as I know every current compiler accepts it.

The optimiser problem was caused by the presence of computed GO TO in well-respected but old code. If fact, the optimiser bug does not arise if the computed GO TO is used the way it was intended to be used, without the 'if (i > 0)then' thrown in as a defensive (?) measure.

The 2003 standard says in 8.2.2:

Execution of a computed GO TO statement causes evaluation of the scalar integer expression. If this value is i such that 1 <= i ⇐  n where n is the number of labels in label-list, a transfer of control occurs so that the next statement executed is the one identified by the i-th label in the list of labels. If i is less than 1 or greater than n, the execution sequence continues as though a CONTINUE statement were executed.

The last sentence in the quote is what makes the optimiser-bug inducing IF..ENDIF superfluous.

28 Apr 2015 12:10 #16249

I had forgotten ASSIGN. My bet was betting on certainty!

28 Apr 2015 5:50 #16251

If you run the F77 fixed form version of the test program but compile using FTN77 4.03 instead of FTN95, the resulting program runs correctly with or without /OPT.

29 Apr 2015 11:29 #16252

What happens if you replace it with:

subroutine cgo(i,j) 
 implicit none 
 integer, intent(in) :: i 
 integer, intent(out) :: j 
 ! 
  select case(i)

    case(1)
      j=3*i-1  

    case(2)
      j=2*i+1  

    case default
      j=0
  end select

end subroutine cgo

Regards Ian

29 Apr 2015 9:21 #16262

Well the test for i>0 in the code fragment below (copied from above) is redundant. Also it doesn't provide any 'safety net' when i = 3 for example.

if (i > 0) then
    go to (10, 20) i
end if   
   j=0;     return
10 j=3*i-1; return
20 j=2*i+1; return

All you need is the following as control goes to the next line if i isn't 1 or 2 because 'go to (10, 20) 3' is the same as 'CONTINUE'.

go to (10, 20) i
j=0;     return
10 j=3*i-1; return
20 j=2*i+1; return

Or use Ian's solution above.

All this said, the purpose of this thread is to point out a bug with the optimiser when /opt is used.

2 May 2015 1:44 #16287

Isn't this the simplest solution (66):

      J = 0
      IF ((I-1)*(I-2)) 20, 10, 20
 10   J = 3*I - 1
 20   CONTINUE

or (77):

      J = 0
      IF ((I-1)*(I-2) .EQ. 0)  J = 3*I - 1

or even:

      J = 0
      IF (I .EQ. 1 .OR. I .EQ. 2)  J = 3*I - 1

The safety net is provided.

Two expressions for J are unnecessary, and if the multiple IFs or computed GOTO must be provided, then surely J=2 and J=5 make calculation redundant.

Whether or not I+I+I-1 or 2*I + I/2 is faster with the routine called many times if a question I leave to others, but certainly it is less clear.

I find that the much unloved arithmetic IF sometimes has a beauty all of its own!

Eddie

2 May 2015 2:23 #16288

Eddie, your modifications do not give the correct result when I = 2, for which J = 2I + 1, not 3I - 1.

I note in this thread a tendency to over-analyse a simple reproducer program. The actual computed GOTO in the larger program was

GOTO  (999,999,130,150,170,190,210,230,250,270,290,310,330,350,500) I

Would you care to tackle such GOTO-s, with the possibility that a target label (e.g., 999) may appear more than once in the list?

Please note that, as far as I know, FTN95 does not have a problem with compiling computed GOTO statements correctly. It is only when the computed GOTO is preceded by an IF statement that the compiler produces incorrect code.

2 May 2015 3:30 #16289

Quoted from mecej4 Eddie, your modifications do not give the correct result when I = 2, for which J = 2I + 1, not 3I - 1.

eh?

22 + 1 = 5 32 - 1 = 5

2 May 2015 4:01 #16290

Mecej4,

your modifications do not give the correct result when I = 2, for which J = 2I + 1, not 3I - 1

For I=2, (not using Fortran syntax)

2x2 = 4 then add 1 = 5 (J = 2*I + 1)

3x2 = 6 then minus 1 = 5 (J = 3*I - 1)

Now where have I gone wrong? For the life of me I can't see where I erred and failed to get the right answer, which is 5. Perhaps I is REAL, and the computed GOTO is of that ghastly short-lived type that was the work of the devil (to quote Dan).

3i-1 = 2i +1 solves to give i=2, if you prefer algebra to arithmetic. True, it only works for i=2, but it (obviously) gives the right answer for i=1 as well.

You were too quick off the mark, old boy!

Of course, but the reproducer problem was a crock of the proverbial, and indeed, the arithmetic GOTO with statement numbers in the full problem is probably the simplest and most elegant solution to the task presented. But then Backus was a genius, and those who followed him might not have been.

The alternative is a long IF THEN ELSE IF construct, or maybe CASE if you prefer.

So your (unnecessary) goal keeper throws FTN95. Certainly needs fixin'.

My own regular goal keeper, as in:

IF (A .EQ. 0.0D0) THEN
     do a whole lot of error reporting, probably resulting in a jump
     ELSE
        C = B/A
        ENDIF

makes FTN95 nag me that comparing to zero is a bad thing to do. Not in this context, Silverfrost me old china. At least when it has finished giving me GBH of the eyeball, it works.

Eddie

2 May 2015 4:19 (Edited: 2 May 2015 4:43) #16291

DavidB,

Your simplicity shames me. I was too busy making coffee, scoffing down a biscuit, and composing what I thought was a witty reply to Mecej4, whose command of the intricacies of the latest Fortran standard usually leaves me open-mouthed in awe and admiration!

Eddie

2 May 2015 4:30 #16292

Ah, now I see what Eddie did! It did not occur to me to examine the content of the target statements to modify the GOTO statement. DavidB and Eddie, you are both correct, but you can see the risk of writing such code, and subsequently modifying, say, 2I + 1 to 2I + 5. And yes, Eddie, you are wickedly witty!

2 May 2015 5:15 #16293

Without wishing to water down the INTENT=BUG_REPORT, but in the interests of INTENT=HISTORICAL_CURIOSITY, in the absence of the computed GOTO, and without logical IFs and suchlike, one might write:

      IF ((I-1)*(15-I)) 80,10,10
10    IF (I-2)  999, 999, 20
20    IF (I-4)  130, 150, 30
30    IF (I-6)  170, 190, 40
40    IF (I-8)  210, 230, 50
50    IF (I-10) 250, 270, 60
60    IF (I-12) 290, 310, 70
70    IF (I-14) 330, 350, 500
80    CONTINUE

Using the ICL Fortran compiler (for Fortran 66 under the George OS on 1900 and 2900 series computers) statement 0 meant 'next line', so this could be written with far fewer statement numbers:

      IF ((I-1)*(15-I)) 10,0,0
      IF (I-2)  999, 999, 0
      IF (I-4)  130, 150, 0
      IF (I-6)  170, 190, 0
      IF (I-8)  210, 230, 0
      IF (I-10) 250, 270, 0
      IF (I-12) 290, 310, 0
      IF (I-14) 330, 350, 500
10    CONTINUE

This whole shebang would have been on cards, so obviously no comments or blank lines ...

Of course, the IFs could have been interspersed with the code, but I've always preferred to have the route map in advance. Indeed, that rules out for me at least, elaborate IF ... THEN ... etc constructs. The first arithmetic IF provides the range check. If efficiency rather than clarity was the goal, the first statement (in standard, not ICL) might be better as:

       IF (I*(16-I)) 80,80,10

By clarity I mean that the statement does not contain the numerical values of the first and last admissible i value, nor the first inadmissible one.

Rather interestingly, one would perhaps change the order of the IF statements and their content to match the frequency with which various I values were likely to crop up to minimise the number of IF statements executed.

And why all this? I believe there is still a role for hand optimisation which (within the limitations of the tools you work with) does not have to lead to difficult to follow code. If I had the list of arithmetic IFs as above, my first step in 'improving' it would be to add a comment or ten explaining what it does, and leave well alone afterwards until I had several idle hours to spend on it.

But I wouldn't write it like that nowadays!

Eddie

2 May 2015 6:41 (Edited: 6 Jun 2016 6:09) #16294

The form IF (arith_expr) nnn, 0, 0 (with '0' standing for 'next line') is new to me. Perhaps, by several decades, this convention predates but closely resembles the instruction-pointer-relative addressing used in the x86 and other processors.

With that special meaning assigned to the label 0, should a card fall out of the hopper while the deck was being read in, you would get no 'missing label nnn' error from the compiler at all, and run-time behavior could be mysterious. I can see that in this situation having card sequence numbers would be vital.

3 May 2015 10:46 #16295

No doubt early Fortran statement types were aligned to processor instructions as one step beyond assembly languages, as distinct from (say) Algol which provides tools to describe algorithms in a standard way. The arithmetic IF goes right back to the beginning.

ICL's zero statement number was an innovative way to reduce the plethora of statement numbers, but it was an extension that no one else adopted. Like all extensions, once you used it on a large scale it impeded transferring the source code to another manufacturer's computers.

The nature of cards was that dropping just one was rather improbable. Drop a box of 2000 at the top of a 5-storey staircase (without risers) - done that. Shred one? Sure. Throw 500 into the air with a high-speed reader jam - that too. The only time I've lost just one line of code was when it was highlighted (but off screen) in a text editor and I pressed another key. It's over 30 years since I used cards, and over 20 since I threw them out.

Eddie

6 Jun 2016 3:10 #17556

The original failure has now been fixed for the next release.

Please login to reply.