ENTRY result cannot be of type REAL(4) in FUNCTION

I’m trying to compile an old code (earliest comment from 1971, latest comment from 1991) with GFortran 11, and getting a new error. I’ve inherited this code, so I’m not really familiar with the ENTRY statement.

Roughly speaking, it looks something like

      function foo (a, b, x, y, cin, zin)
      implicit integer*4 (a-c, i-z),  real*8 (d-h)
      real*8 foo
      real*4 bar
      entry bar (a, b, x, y, cin,zin)
...

The compile error is then

    4 |       real*4 bar
      |                1
Error: ENTRY result bar cannot be of type REAL(4) in FUNCTION foo at (1)

Any ideas what this error means, or how to diagnose it?

1 Like

From the standard, Sec. 15.6.2.6 ENTRY statement

If the ENTRY statement is in a function subprogram, an additional function is defined by that subprogram. The name of the function is entry-name and the name of its result is result-name or is entry-name if no result-name is provided. The dummy arguments of the function are those specified in the ENTRY statement. If the characteristics of the result of the function named in the ENTRY statement are the same as the characteristics of the result of the function named in the FUNCTION statement, their result names identify the same entity, although their names need not be the same. Otherwise, they are storage associated and shall all be nonpointer, nonallocatable scalar variables that are default integer, default real, double precision real, default complex, or default logical.

real*4 and real*8 aren’t the same size, so they can’t be storage associated. This looks like somebody was trying to play a trick to get a function with two different return types/kinds without having to rewrite the contents, but that’s not allowed.

2 Likes

Yep, that was exactly what they were doing! (They even documented the behavior in a comment.) Given that the bar version just does bar = real(foo) before returning, I should be able to just write a separate bar function which calls foo and does a conversion, right? I assume a modern compiler will simply do the appropriate inlining anyway.

1 Like

That’s probably what I would do.

2 Likes

Just to follow up, I discovered that this was actually a problem with the build system. Surprisingly, this code builds just fine unless one uses gfortran’s -fdefault-real-8 option… this file was not being built with the correct flags. Well, of course, I’m not sure any of this is “correct” but that’s a different issue…

1 Like

I am told (by one of the developers of the code) that its use is entirely to ensure that, e.g., 1. is interpreted as 1.d0, i.e. as a double-precision floating point constant, rather than a single-precision floating point constant.

1 Like

That’s exactly what I thought when I saw the code. Old programmers used to do such tricks (and even worse) to deal with the limitations of the time, such as saving a few bytes of RAM. Admittedly, some of those tricks were very cleverly crafted, but still a common source of confusion.

2 Likes

From what I can tell, there is not much concern here about standards or conformance… I’m just trying to get this built and into a state that I can start working on it with fpt.

1 Like

This was a problem with trying to write portable f77 (and earlier) code, but it is not a problem with modern fortran. Just change the 1.0 to 1.0_wp, and then define wp to be the appropriate value. If wp is defined in a single place, and its value is propagated throughout the program, then you can change the precision of the whole program by changing just that one line of code and recompiling. That one level of abstraction in modern fortran, which was nonexistent in f77 and earlier, is like magic.

BTW, I would never recommend that anyone use a D exponent in modern code, or a DOUBLE PRECISION declaration. That should now be obsolescent.

Good point. That is one way to get the double precision KIND value while keeping the advantages of the fortran KIND system in play. As for “how else”, there is selected_real_kind(), along with real64 and the real_kinds(:) array from the iso_fortran_env module. As for the portability of these various approaches, you yourself just warned against the use of compiler options such as -fdefault-real-8 because of, among other reasons, how these can interact adversely with the KIND system.

1 Like

This gets back to the issue of why it was so difficult to write portable code with f77 (and earlier). If your application required 64-bit precision (or the closest approximation that was available) on a wide range of hardware and compilers, there was no way to achieve that goal. On some machines, you needed to use REAL while on others you needed to use DOUBLE PRECISION. And that filtered all the way through the programming infrastructure. Do you call SAXPY() or DAXPY(), do you specify constants as 1.0E0 or as 1.0D0, and on and on. It simply was not possible in standard fortran to achieve that goal. So people “cheated” with REAL*8 and with preprocessors and with (nonstandard) INCLUDE, and in various other ways, all of which were outside of standard fortran.

So when you point out that REAL64 is guaranteed to give 64 bit floating point, but DOUBLE PRECISION is not, you are really just making the case, yet again, that REAL and DOUBLE PRECISION are not portable, while REAL64 is portable. This is the same case that you made previously regarding -fdefault-real-8, but now you are arguing from the other perspective.

As far as which approach one uses, selected_real_kind(), etc., that is something that each programming project can decide. If everyone in the project agrees that selected_real_kind(14) is the right precision to use and that it works the required/expected way on a wide variety of compilers and hardware, then that sounds to me like that would be the most portable approach. If they decide to use REAL64 from iso_fortran_env, then that is another way to write portable code. But REAL and DOUBLE PRECISION declarations scattered throughout your program are definitely not the way to write portable code, and we’ve known that for several decades (now, almost 50 years for me).

I have no practical objection to using wp=REAL(1.0D0) either, as long as it isn’t spread throughout the program in thousands of different places. Put it in one place, and then propagate it from there throughout the rest of the program. Then if you do ever want to change it, because some future compiler decides to treat REAL64 as the default real and REAL128 as DOUBLE PRECISION, then you just need to change that one line of code to get the precision that you want.

I do agree that there is a quality of implementation issue in all of this. If some future compiler decides that REAL64 is -1, and that selected_real_kind(14) returns an 80-bit floating point instead of the 64-bit floating point that you want to use, and that 1.0D0 is a 128-bit floating point, then you would need to make some changes to your code. If your KIND values are defined in a single place in your code, and those values are propagated from there throughout, then not even those choices would be real obstacles, you could still work around them by changing a single line of code, even using a literal integer constant if necessary. That extra level of abstraction is almost magical in the flexibility it gives the programmer and in the numerous problems that it solves.

1 Like

I replied to this comment previously, but it occurred to me that maybe I should add to my previous comments. The -fdefault-real-8 option in gfortran (and similar options in other compilers) do not ensure that constants are interpreted as double precision. With that option, the default REAL becomes 64-bit floating point and DOUBLE PRECISION becomes 128-bit floating point. A default REAL constant is still a REAL constant, it is that the definition of REAL has changed (in gfortran, from KIND=4 to KIND=8). If that constant were interpreted instead as DOUBLE PRECISION, it would then have become a 128-bit floating point constant, which is KIND=16 in gfortran. That’s not what happens, and if it did happen, then your code might slow down by a factor of 8 or so because 128-bit floating point (emulated in software with some hardware support) is slow compared to the 32-bit and 64-bit arithmetic that has full hardware support.

As @kargl notes, there are other inconsistencies that can creep into the code with these compiler options. External symbols are typically not remapped, so now you need to reference DDOT, DAXPY, DGEMM, and so on, even when all your variables are declared as default REAL and all of your literal constant arguments have E exponents instead of D exponents (or no exponent at all, which is the same as an E exponent).

This slippery association is all because fortran doesn’t (and never did) define what REAL and DOUBLE PRECISION are. It only defines certain properties, particularly those related to storage association between the two data types (or kinds, in modern fortran). This is unlike, for example, the IEEE definitions of floating point types, in which single precision REAL is defined as 32-bits and DOUBLE PRECISION is 64-bits. IEEE makes those associations, but fortran does not. And when it comes to compiler options such as -fdefault-real-8, there is clearly a conflict in the meanings, which the programmer must keep straight in order to produce a correct and consistent working program.

Consider this small program:

program xxx
   real(4) :: r4
   real(8) :: r8
   real(16) :: r16
   write(*,*) kind(r4), kind(r8), kind(r16), kind(1.E0), kind(1.D0), kind(1)
end program xxx

$ gfortran xxx.F90 && a.out
           4           8          16           4           8           4
$ gfortran -fdefault-real-8 xxx.F90 && a.out
           4           8          16           8          16           4

Notice how the KIND values change for the real constants. Note also that the default integer KIND does not change when the default REAL kind changes. Another compiler might make that change too, in order to keep the fortran storage association rules consistent between integer and real KINDS. That’s one more thing the programmer must keep straight when using these types of compiler options.

1 Like

@RonShepard Thanks for the additional clarification. This brings up an additional question then. How exactly does -fdefault-real-8 and -fdefault-double-8 affect the following definitions, assuming I’m running on a standard x86-64 platform?

      real a1
      real*4 a2
      double precision b1
      real*8 b2

Similarly, how does it affect implicit declarations, like

      implicit real*4 (a)
      implicit real*8 (b)
...
      a = 1.0
      b = 1.0

I would have originally assumed that -fdefault-* options only affected real and double precision but not real*4 and real*8. Is that incorrect?

I would not attempt to answer that without trying the code first.

program xxx
   real(4) :: r4
   real(8) :: r8
   real(16) :: r16
   real*4 s4
   real*8 s8
   real*16 s16
   write(*,'(*(i0,1x))') kind(r4), kind(r8), kind(r16), kind(1.E0), kind(1.D0), &
        & kind(1), kind(s4), kind(s8), kind(s16)
end program xxx

$ gfortran xxx.F90 && a.out
4 8 16 4 8 4 4 8 16
$ gfortran -fdefault-real-8 xxx.F90 && a.out
4 8 16 8 16 4 4 8 16

It looks like REAL*N and REAL(N) are treated the same. Of course, other compilers might behave differently.

1 Like

It seems like the same rules apply for implicit statements as well.