Test code for investigating possible bug in GFORTRAN compiling code generated by SFTRAN3 pre-processor
Usage:
make run
make run MYFFLAGS=
Both commands create and run two executables: one built by G77; one built by GFORTRAN
The former command [make run] will produce executables that are both successful.
The latter command [make run MYFFLAGS=] will produce a successful executable with G77, but an unsuccessful executable with GFORTRAN.
In all cases, the code appears to be semantically correct, but in the latter case GFORTRAN generates an executable that returns .TRUE. from logical function NCSCAN when it should return .FALSE. The flow of the program looks like the following; the key point is the [IF (.NOT.(NCSCAN)) GO TO 20014] statement, which transfers control to 20014 when NCSCAN is .FALSE., which control is then transferred to 20009, which in turn then returns control to the caller, but it returns .TRUE. instead of the correct .FALSE. value when GFORTRAN is the compiler. Replacing the [GO TO 200014] with [RETURN] makes GFORTRAN compile an executable that runs correctly.
LOGICAL FUNCTION NCSCAN(...)
[...]
GOTO 30002
20009 RETURN
30002 [...]
NCSCAN = [expression that evaluates to .TRUE. or .FALSE.
IF (.NOT.(NCSCAN)) GO TO 20014
[...]
20014 GO TO 20009
There are four FORTRAN source files in this repository: bbpas1.f is the main program; ncscan.f contains the code that GFORTRAN compiles incorrectly; batop2.f and nncmpr.f are support files. Comments in the code explain what is happening; the original sources are from the SFTRAN3 pre-processor itself, and the executable tries to pre-process a single PARAMETER statement, but the details are not important. What is important to note is that G77 compiles an executable that works correctly, and GFORTRAN does not.
There is a fifth FORTRAN source file: test_zero.f; see Minimal test case below for details.
> make run
g77 -x f77-cpp-input -g -O0 -finit-local-zero -fno-automatic -DARGRTN=1 bbpas1.f batop2.f ncscan.f nncmpr.f -o y_g77.e -B/usr/lib/x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/5
gfortran -cpp -std=legacy -g -O0 -finit-local-zero -fno-automatic -DARGRTN=1 bbpas1.f batop2.f ncscan.f nncmpr.f -o y_gfortran.e
./y_g77.e
OKAY: NCSCAN says [PARAME].NE.[SFIELD]
./y_gfortran.e
OKAY: NCSCAN says [PARAME].NE.[SFIELD]
./test_zero_g77.e
F = zero from inside callee zero
F = lcl_zero<=zero() from caller test_zero
./test_zero_gfortran.e
F = zero from inside callee zero
T = lcl_zero<=zero() from caller test_zero
> make run MYFFLAGS=
g77 -x f77-cpp-input -g -O0 -finit-local-zero -fno-automatic bbpas1.f batop2.f ncscan.f nncmpr.f -o y_g77.e -B/usr/lib/x86_64-linux-gnu -L/usr/lib/gcc/x86_64-linux-gnu/5
gfortran -cpp -std=legacy -g -O0 -finit-local-zero -fno-automatic bbpas1.f batop2.f ncscan.f nncmpr.f -o y_gfortran.e
./y_g77.e
OKAY: NCSCAN says [PARAME].NE.[SFIELD]
./y_gfortran.e
FAIL: NCSCAN says [PARAME].EQ.[SFIELD]
STOP NCSCAN indicates MATCH when there should be none
GNU Fortran (Ubuntu 5.4.0-6ubuntu1~16.04.11) 5.4.0 20160609
Copyright (C) 2015 Free Software Foundation, Inc.
GNU Fortran comes with NO WARRANTY, to the extent permitted by law.
You may redistribute copies of GNU Fortran
under the terms of the GNU General Public License.
For more information about these matters, see the file named COPYING
Linux s76dad 4.4.0-157-generic #185-Ubuntu SMP Tue Jul 23 09:17:01 UTC 2019 x86_64 x86_64 x86_64 GNU/Linux
NAME="Ubuntu"
VERSION="16.04.6 LTS (Xenial Xerus)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 16.04.6 LTS"
VERSION_ID="16.04"
HOME_URL="http://www.ubuntu.com/"
SUPPORT_URL="http://help.ubuntu.com/"
BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/"
VERSION_CODENAME=xenial
UBUNTU_CODENAME=xenial
Unfortunately, I don't have time at present to fully debug this and submit a patch to GFORTRAN, which would be my preferred route. However, see Analysis below
Brian Carcich, Latchmoor Services, LLC, BrianTCarcich@gmail.com, 2019-08-03
There is a minimal test case in test_zero.f; it should print [F = ...] twice when run, and it does when compiled with G77; however it does not when compiled with GFORTRAN.
=================================================================== ========================================================================
===> FAILing compiled assembly for ncscan.f on the left ===> SUCCESful compiled assembly for ncscan.f on the right
===> - Compiled with gfortran MYFFLAGS=-DARGRTN ... ===> - Compiled with gfortran MYFFLAGS= ...
=================================================================== ========================================================================
108 30002 NCSCAN = MORE .AND. (CCMORE+N-1.LE.NS) 108 30002 NCSCAN = MORE .AND. (CCMORE+N-1.LE.NS)
109 * .AND. NNCMPR(STRING,1,STMT,CCMORE,N) 109 * .AND. NNCMPR(STRING,1,STMT,CCMORE,N)
117 IF (.NOT.(NCSCAN)) GO TO 20014 | 125 IF (.NOT.(NCSCAN)) RETURN
.LBB2: .LBB2:
.loc 1 30 0 .loc 1 30 0
mov eax, DWORD PTR c04_[rip+16] # D.XXXX, c04.more mov eax, DWORD PTR c04_[rip+16] # D.XXXX, c04.more
Test eax (AND eax with itself); jump to .L5 if eax ends up as zero after that, i.e. [more], is .FALSE.
test eax, eax # D.XXXX test eax, eax # D.XXXX
je .L5 #, je .L5 #,
mov rax, QWORD PTR [rbp-40] # tmpXXX, n mov rax, QWORD PTR [rbp-40] # tmpXXX, n
mov edx, DWORD PTR [rax] # D.XXXX, *n_1X(D) mov edx, DWORD PTR [rax] # D.XXXX, *n_1X(D)
mov eax, DWORD PTR c04_[rip+8] # D.XXXX, c04.ccmore mov eax, DWORD PTR c04_[rip+8] # D.XXXX, c04.ccmore
add eax, edx # D.XXXX, D.XXXX add eax, edx # D.XXXX, D.XXXX
lea edx, [rax-1] # D.XXXX, lea edx, [rax-1] # D.XXXX,
mov eax, DWORD PTR c02_[rip] # D.XXXX, c02.ns mov eax, DWORD PTR c02_[rip] # D.XXXX, c02.ns
Compare edx [CCMORE+N-]) to eax [NS]; jump to .L5 if edx [CCMORE+n-1] is greater than eax [NS] i.e. if the expression [CCMORE+N-1.LE.NS] is .FALSE.
cmp edx, eax # D.XXXX, D.XXXX cmp edx, eax # D.XXXX, D.XXXX
jg .L5 #, jg .L5 #,
mov rdx, QWORD PTR [rbp-40] # tmpXXX, n mov rdx, QWORD PTR [rbp-40] # tmpXXX, n
[...]
call nncmpr_ # call nncmpr_ #
add rsp, 16 #, add rsp, 16 #,
test eax, eax # D.XXXX test eax, eax # D.XXXX
je .L5 #, je .L5 #,
To here, none of the expressions connected by .AND. were .FALSE.; put 1 (.TRUE.) into eax and unconditially jump to .L6
mov eax, 1 # D.XXXX, mov eax, 1 # D.XXXX,
jmp .L6 # jmp .L6 #
Target .L5 puts zero (.FALSE.) into register eax; we got here because at least one of the .AND.-ed expressions in line 109 was .FALSE.
.L5: .L5:
mov eax, 0 # D.XXXX, mov eax, 0 # D.XXXX,
.L6: .L6:
mov DWORD PTR [rbp-4], eax # __result_ncscan, D.XXXX mov DWORD PTR [rbp-4], eax # __result_ncscan, D.XXXX
117 IF (.NOT.(NCSCAN)) GO TO 20014 | 125 IF (.NOT.(NCSCAN)) RETURN
.LBE2: .LBE2:
-
Copy NCSCAN result to eax => eax=1 or 0 if NCSCAN is .TRUE. or .FALSE, respectively.
-
XOR eax with 1 => eax=0 or 1 if NCSCAN is .TRUE. or .FALSE., resp.
-
TEST eax => eax=0 or 1 if NCSCAN is .TRUE. or .FALSE., resp
.loc 1 117 0 is_stmt 1 discriminator 8 | .loc 1 125 0 is_stmt 1 discriminator 8 mov eax, DWORD PTR [rbp-4] # D.XXXX, __result_ncscan mov eax, DWORD PTR [rbp-4] # D.XXXX, __result_ncscan xor eax, 1 # D.XXXX, xor eax, 1 # D.XXXX, test eax, eax # D.XXXX test eax, eax # D.XXXX
The successful case on the right jumps to .L7 with eax set to 0 i.e. if the [NHSCAN result] is .TRUE.
jne .L20 #, | je .L7 #,
Here is the essential difference for the successful case on the right when the [NHSCAN result] is .FALSE.:
-
The value of the [NHSCAN result] is moved to eax i.e. eax becomes 0.
-
The successful case then jumps unconditionally to .L1.
-
N.B. in the failing case, eax is 0 when the jump to .L20 occurs (see above)
> mov eax, DWORD PTR [rbp-4] # D.XXXX, __result_ncscan > jmp .L1 #
and drops through to .L7
We don't show the code for when eax (__result_ncscan) is .TRUE., because that case is not where we see the discrepancy between G77 and GFORTRAN
.L7:
.loc 1 131 0
[...]
Targets [.L20 on the left for the failing case] and [.L1 on the right for the successful case] are equivalent and result in a RETURN from NCSCAN
- N.B. however, for the case where the [NCSCAN result] was calculated to be .FALSE:
- Register eax will be 1 for the failing case on the left, and
- Register eax will be 0 for the successful case on the right.
.L20: | .L1:
.loc 1 139 0 <
nop <
.loc 1 90 0 <
nop <
.loc 1 193 0 .loc 1 193 0
leave leave
.cfi_def_cfa 7, 8 .cfi_def_cfa 7, 8
ret ret
.cfi_endproc .cfi_endproc
.cfi_endproc .cfi_endproc
FORTRAN lines 90, 117, 139 and 193 are shown here so the user can track how the failing case gets to RETURN and END for NHSCAN.
90 20009 RETURN
117 IF (.NOT.(NCSCAN)) GO TO 20014
139 20014 GO TO 20009
193 END
- When the [NCSCAN result] is calculated as .FALSE. on line 109 of ncscan.f, the GFORTRAN-compiled code on the left incorrectly returns .TRUE. via register eax to the caller.
- When the [NCSCAN result] is calculated as .FALSE. on line 109 of ncscan.f, the GFORTRAN-compiled code on the right correctly returns .FALSE. via register eax to the caller.