FORTRAN "gotchas", and how to aviod them 
Author Message
 FORTRAN "gotchas", and how to aviod them

What do you think are the worst fortran "gotchas", and how can you
get around them?

Here's a few I try to avoid like the plague, and the means of
avoiding them:

Inconsistent arguments for FUNCTIONs or SUBROUTINES: Use tools like
        lintfor, Toolpack or whatever else.  Do not modify parameters
        which might be constants in the calling routine.  Turn on
        all debugging options in sight, especially range checking.

Inconsistent COMMON blocks: use include files for COMMON and PARAMETER
        statements.  Use make(1) or equivalent to keep track of
        header files.

Mistyped variable names: Use IMPLICIT NONE

Uninitialized variables:  If at all possible with the compiler, set all
        uninitialized variables to a signalling NaN (I just wish
        this compiler option was more common...)  Use SAVE when you
        suspect it might be needed.

If I have "dusty deck" code, I usually run it through Toolpack to get
some sense into it, then try the above techniques.

Anything important I've missed?
--

The joy of engineering is to find a straight line on a double
logarithmic diagram.



Sun, 13 Aug 1995 22:27:40 GMT  
 FORTRAN "gotchas", and how to aviod them
tk> Thomas Koenig

tk> What do you think are the worst FORTRAN "gotchas", and how can you get
    around them?
    [...]
tk> Anything important I've missed?

  My personal favorite is the use of the adjacent memory locations (as the
  definition of COMMON requires) to access "out-of-bounds" array elements:

      COMMON /ABC/ VAR1,VAR2(10),VAR3
      [...]

      VAR1 = 10.
      VAR2 =  0.1
      DO I = 1,10
         CMPVAL = VAR2(I - 1) * 3.
         VALCMP = VAR2(I + 1) / 4.
         [...]

  You're saying "Hey, what about VAR2(0) and VAR2(11)? They don't exist!"
  Oh yes, they do! VAR2(0) = VAR1, VAR2(11) = VAR3.

  This can result in some awfully difficult debugging, that's fer shur.

--
Gary Strand                      Opinions stated herein are mine alone and are



Mon, 14 Aug 1995 01:26:36 GMT  
 FORTRAN "gotchas", and how to aviod them
This is my most recent one:

      ...
      call sub( in1, iout1, idummy, idummy )
      ...
      subroutine sub( in1, iout1, iout2, iout3 )
      iout1 = 0
      iout2 = 0
      iout3 = 0
      ...
      iout1 = ...
      iout2 = ...
      iout3 = ...
      return
      end

The gotcha? I was only interested in the first output argument, so I just put
place-holders in the argument slots in the call statement.  However,
internally, sub() used iout2 and iout3 to store intermediate results.  Since
they were equivalenced by call, they kept overwriting each other internally,
but the results depended on the optimization level.  With optimization, iout2
and iout3 were stored in registers within sub(), copied back to memory only at
the return, and produced correct results; without optimization, the
equivalence-by-call got me.

sub() was an old routine, which I trusted to be correct (and it was, in a
sense).  Meanwhile, I was tracking down another error in a separate routine.
My error kept moving around on me as I recompiled pieces of the code, so you
get the picture...

This may have finally broke me of this bad habit. :-)

I would be interested in comments on the best way to "fix" this bug.

(1) Should all routines be written such that they do not modify dummy arguments
until their final values have been computed?

(2) Should routines never be called with repeated "place-holder" type arguments
(which are never referenced otherwise).

(3) Apply both (1) and (2) above.

(4) Another alternative?

-Ron Shepard



Mon, 14 Aug 1995 07:03:48 GMT  
 FORTRAN "gotchas", and how to aviod them

Quote:

>This is my most recent one:
>      ...
>      call sub( in1, iout1, idummy, idummy )
>      ...
>      subroutine sub( in1, iout1, iout2, iout3 )
>The gotcha? I was only interested in the first output argument, so I just put
>place-holders in the argument slots in the call statement.  However,
>internally, sub() used iout2 and iout3 to store intermediate results.
>(1) Should all routines be written such that they do not modify dummy arguments
>until their final values have been computed?

This is probably too extreme, although it does make the routine more
bulletproof.

Quote:
>(2) Should routines never be called with repeated "place-holder" type arguments
>(which are never referenced otherwise).

This is the right approach, especially for arguments that are modified by
the called routine.  Fortran compiler optimizers are allowed by the standard
to rely on the fact that there aren't any hidden aliases between parameters
and common variables that are modified, and if you (as a programmer)
violate this assumption you can get hurt badly (as you found out ;-).

This is arguably one of the major performance advantages of Fortran over C;
namely that the compiler can make optimistic assumptions about
parameter aliasing.  We vector-machine types find this especially useful,
as otherwise we couldn't vectorize even trivial DO loops that involve
more than one dummy argument array, such as
      subroutine s(a,b,n)
      do 1 i = 1,n
    1 a(i) = b(i)
      end
without doing some kind of run-time overlap analysis.  Even scalar machines
can benefit from this; in the above example the loop can be unrolled and
instruction-scheduled on many machines if it's known to be safe to reorder
the memory references to A and B.



Tue, 15 Aug 1995 00:25:29 GMT  
 FORTRAN "gotchas", and how to aviod them

   This is my most recent one:

         ...
         call sub( in1, iout1, idummy, idummy )
         ...
         subroutine sub( in1, iout1, iout2, iout3 )
         iout1 = 0
         iout2 = 0
         iout3 = 0
         ...
         iout1 = ...
         iout2 = ...
         iout3 = ...
         return
         end

This code is not standard-conforming.  Basically the standard imposes
restrictions on ANSI FORTRAN 77 code such that, when looking at a given
program unit in isolation, you (or the compiler) should be able to
deterrmine via _static analysis_ all possible aliases involving
variables/arrays that become redefined or undefined during any
invocation of the program unit.  (This is one of the ways Fortran is
"different" from C -- this feature permits better optimization to be
done for some machines without resorting to program-wide global
optimization, but it places a greater burden on the programmer to
avoid "misusing" the feature.)

Since iout2 and iout3 are aliased at run time and at least one of them
becomes redefined during execution of sub(), it is invalid for them
to be equivalenced by a caller of sub().  It would be okay if iout2
and iout3 were "read-only" in sub() (and all procedures it calls, of
course).  Again, the problem is that the compiler cannot tell that
iout2 and iout3 might be aliased just by looking at sub().

The same problem arises passing a COMMON variable, say FOO, to a
procedure that also sees the same COMMON variable (i.e. it or any of
its subprocedures declares the same COMMON area) and that modifies
either FOO directly or via the dummy argument aliased to it.

Generally, just imagine that each Fortran procedure is implemented
starting with code that does this:

For every dummy argument (ignoring the fact that we don't really know
    the sizes of assumed-size arrays) and every COMMON area known by
    this procedure:
  Allocate scratch memory to hold the entity (variable/array/COMMON)
  Copy entity to scratch memory
  Use scratch memory instead of entity for all accesses

And before returning, imagine it does this as well:

For every dummy argument and COMMON area (same list as above):
  Copy scratch memory back to entity
  Deallocate scratch memory

(For completeness, before calling any other procedure that might know about a
given COMMON area, it does this:

Copy COMMON area's scratch memory back to entity

And after the call:

Copy COMMON entity back to scratch memory

Also, any of the scratch-memory copies of the entities may be arbitrarily
flushed back to their official storage and perhaps resurrected to the
scratch-memory versions at any time by the implementation as long as
accesses to the scratch-memory versions are properly managed -- for example,
around a procedure call to leave the called procedure plenty of scratch
memory.)

E.g. the arguments and COMMONs could be passed in a disk file via
record numbers and the like, and the above operation could be how
each procedure collects the data it needs to run fast (i.e. without
disk I/O except at procedure boundaries).

Note that none of the above takes into account possible aliases or
defines the order in which copying takes place.  And the standard
permits the above implementation (modulo any bugs I might have in
my pseudo-code :-).

If your code can withstand an implementation like the above, it
should be standard-conforming with respect to the anti-aliasing rules.

Do implementations exist that act like the above?  Yes, amazingly enough.

   This may have finally broke me of this bad habit. :-)

It's a {*filter*}e to even notice you have.  It'd be somewhat easier with
Lint tools or things like Fortran 90's interface definitions (so the
compiler could warn if arguments passed to procedure that might modify
them, i.e. with other than the "IN" attribute for "read-only", appear
to be aliased with each other or COMMON, but even that isn't enough as
far as I know).

   I would be interested in comments on the best way to "fix" this bug.

   (1) Should all routines be written such that they do not modify dummy arguments
   until their final values have been computed?

No.  Write your code the way you think is best.  Changing the point at
which dummy arguments are modified within a procedure will not necessarily
solve the problem at hand -- consider the mythical implementation I describe
above.

   (2) Should routines never be called with repeated "place-holder" type arguments
   (which are never referenced otherwise).

Not necessarily -- it's okay as long as the dummy arguments are read-only.
Also, it's not enough, since aliases can creep in even if you follow
such a rule (e.g. "CALL ARRAYMULT (OPRND1, OPRND2, OPRND1, N)" where
the third argument is supposed to be the array that holds the result of
the operation).

   (3) Apply both (1) and (2) above.

Somewhat better.

   (4) Another alternative?

Basically, it should be a defined attribute of each routine whether it
defines/redefines/undefines an argument.  No caller should pass
one place-holder actual argument to two or more arguments where at least
one of them is defined/redefined/undefined, or problems will likely result
(and, in any case, the code will not be standard-conforming).

NOTE: there is a subtle distinction between static behavior and run-time
behavior as far as the standard is concerned.  Generally, we should
view these issues in terms of static behavior (either a procedure
changes a dummy argument or it doesn't), but the standard talks about
run-time behavior only here.

So, the following code is actually standard-conforming, but it should be
avoided because, to human eyes, it appears to violate that anti-aliasing
restriction in the standard:

        CALL SUB (.FALSE., IDUMMY, IDUMMY)

        SUBROUTINE SUB (CHANGE, IOUT2, IOUT3)
        LOGICAL CHANGE
C
        IF (CHANGE) IOUT2 = IOUT3 + 1
C
        END

The code conforms because when the CALL SUB is performed, that particular
invocation of SUB won't actually modify either of the aliased arguments.
(Change .FALSE. to .TRUE. and the code stops conforming.)

But unless it is really important to think of SUB's arguments as
conditionally IN, OUT, or INOUT, it is best to think of them as
(respectively, for example) (CHANGE:IN, IOUT2:OUT, IOUT3:IN), which
are static (compile-time) considerations.  And, viewed this way, we
should know better than to pass a single variable for the latter
two arguments, since at least one of them is "defined as" being modified.
--





Mon, 14 Aug 1995 18:19:15 GMT  
 FORTRAN "gotchas", and how to aviod them

Quote:

> What do you think are the worst FORTRAN "gotchas", and how can you
> get around them?

[... Nice examples omitted ...]

Quote:
> Anything important I've missed?

How about undeclared external functions ? Today I saved someone a bad  
night by noticing that

        write(6,*) c(ai(icomp), 15)             ! c is an 2D array ...

could return a bogus value if 'ai' wasn't declared integer (I first  
thought it was an array, but it turned out to be an integer function).  
Some compilers 'know' that you don't want to index arrays with reals, but  
some just truncate the supposed real and go on - happily ever after ...

A new one - that isn't a gotcha really, because the compiler 'catches it',  
is the use of non-generic names for intrinsic functions. Lately I've been  
bitten by compilers that think that:

        real x, y

        x = alog(y)

is an error when the default size of a real is 64 bits. Needless to say, a  
lot of users write this kind of code because they're afraid the implicit  
typing of FORTRAN will make log(y) an integer :-) :-) (Yes, I know the  
fail-safe solution is to put 'INTRINSIC LOG' somewhere in the declaration  
part of the program, but try to get that across to people who've never had  
to do that).

--

Kantershof 269, 1104 GN  Amsterdam, The Netherlands
Phone: + 31 20 6982029; Fax: + 31 20 6003411
No Disclaimers; a NeXT at home protects against this occupational hazard.



Tue, 15 Aug 1995 04:43:38 GMT  
 FORTRAN "gotchas", and how to aviod them

[text deleted...]

I agree that my "gotcha" is not standard conforming.

Quote:
>Generally, just imagine that each Fortran procedure is implemented
>starting with code that does this:

>For every dummy argument (ignoring the fact that we don't really know
>    the sizes of assumed-size arrays) and every COMMON area known by
>    this procedure:
>  Allocate scratch memory to hold the entity (variable/array/COMMON)
>  Copy entity to scratch memory
>  Use scratch memory instead of entity for all accesses

>And before returning, imagine it does this as well:

>For every dummy argument and COMMON area (same list as above):
>  Copy scratch memory back to entity
>  Deallocate scratch memory

But this is a not a good example of why it is bad.  If this had
been the case then my code would have always worked.  In fact, this
was the case with optimization; the internal calculations were done
in registers and copied back to memory only at the end.  It was
only while compiling the code with debugging options that problems
occurred because of the aliasing.

[text deleted...]

Quote:
>Basically, it should be a defined attribute of each routine whether it
>defines/redefines/undefines an argument.  No caller should pass
>one place-holder actual argument to two or more arguments where at least
>one of them is defined/redefined/undefined, or problems will likely result
>(and, in any case, the code will not be standard-conforming).

This is good advice.

When I (and most other programmers) use dummy arguments like this,
I expect them to be undefined and unusable upon return.  The
problem in my code was that this undefined nature was propagated
into the returned values of arguments that I did want.

-Ron Shepard



Tue, 15 Aug 1995 08:52:40 GMT  
 FORTRAN "gotchas", and how to aviod them

Quote:
Moene) writes:
> A new one - that isn't a gotcha really, because the compiler 'catches  
it',  
> is the use of non-generic names for intrinsic functions. Lately I've  
been  
> bitten by compilers that think that:

>    real x, y

>    x = alog(y)

> is an error when the default size of a real is 64 bits.

There is a word for compilers like this. They are called "broken."
Seriously, I would like to know what compiler has this problem. The  
compilers I have used where reals are 64 bits will still accept this, as  
required by the standard. Granted, it is better to use the generic LOG  
rather than the specific ALOG; nevertheless, ALOG is specified to take  
REAL arguments and therefore a compiler has no business objecting to this  
code.

The standard does not say how many bits there are in a REAL, by the way,  
unless you are talking about Fortran 90. Maybe you are, since you talk  
about "the DEFAULT size of a real", as if you had a choice, which you  
don't in Fortran 77. You take whatever the implementation gives you.

--
Dave Seaman



Wed, 16 Aug 1995 00:36:37 GMT  
 FORTRAN "gotchas", and how to aviod them

Quote:
(Dave Seaman) writes:

> Moene) writes:
> > A new one - that isn't a gotcha really, because the compiler 'catches  
> it',  
> > is the use of non-generic names for intrinsic functions. Lately I've  
> been  
> > bitten by compilers that think that:

> >       real x, y

> >       x = alog(y)

> > is an error when the default size of a real is 64 bits.

> There is a word for compilers like this. They are called "broken."
> Seriously, I would like to know what compiler has this problem. The  
> compilers I have used where reals are 64 bits will still accept this, as  
> required by the standard. Granted, it is better to use the generic LOG  
> rather than the specific ALOG; nevertheless, ALOG is specified to take  
> REAL arguments and therefore a compiler has no business objecting to
> this code.

> The standard does not say how many bits there are in a REAL, by the way,  
> unless you are talking about Fortran 90. Maybe you are, since you talk  
> about "the DEFAULT size of a real", as if you had a choice, which you  
> don't in Fortran 77. You take whatever the implementation gives you.

Yes, 'The Standard' (i.e. FORTRAN 77's standard) doesn't know about sizes  
of reals, integers, or logicals (it might even not know about sizes of  
characters - I've supported FORTRAN 77 compilers on UNISYS machines that  
used 9 bits for a character - and 36 for an integer). This, however, is  
beside the point: FORTRAN gotcha's. As we are shifting from 32 bit  
(integer and floating point) to 64 bit machines, vendors have to find a  
way to give programmers access to both sizes of these types. The point is  
that nobody - at least in my environment - ever believed that alog an dlog  
were really different - they were shielded from reality by Cray Research  
Incorporated's computers, and compilers that adhered to the 'de facto'  
standards issued therefrom. Now that we consider to port our applications  
to workstation-like hardware, we're faced with the possibility that  
integers and reals may not be 64 bits and that alog and dlog may really be  
different beasts. So, although a REAL does not have a defined number of  
bits according to the Standard, vendors have to supply FORTRAN compilers  
that define that number (based on a compilation switch, e.g.) to give  
users access to the data types supported by the hardware. Consequently,  
they have to enforce a particular meaning to alog and dlog.

--

Kantershof 269, 1104 GN  Amsterdam, The Netherlands
Phone: + 31 20 6982029; Fax: + 31 20 6003411
No Disclaimers; a NeXT at home protects against this occupational hazard.



Thu, 17 Aug 1995 03:43:48 GMT  
 FORTRAN "gotchas", and how to aviod them
   I agree that my "gotcha" is not standard conforming.

   >Generally, just imagine that each Fortran procedure is implemented
   >starting with code that does this:
   >
   >For every dummy argument (ignoring the fact that we don't really know
   >    the sizes of assumed-size arrays) and every COMMON area known by
   >    this procedure:
   >  Allocate scratch memory to hold the entity (variable/array/COMMON)
   >  Copy entity to scratch memory
   >  Use scratch memory instead of entity for all accesses
   >
   >And before returning, imagine it does this as well:
   >
   >For every dummy argument and COMMON area (same list as above):
   >  Copy scratch memory back to entity
   >  Deallocate scratch memory

   But this is a not a good example of why it is bad.  If this had
   been the case then my code would have always worked.  In fact, this
   was the case with optimization; the internal calculations were done
   in registers and copied back to memory only at the end.  It was
   only while compiling the code with debugging options that problems
   occurred because of the aliasing.

   [text deleted...]

You deleted the parenthetical info that illustrates just the behavior
you saw, so it was a good example of why it is bad.  Essentially, the
behavior you saw did the "spill" of "heap memory" on every statement
or expression-evaluation, not just every procedure call.

   This is good advice.

   When I (and most other programmers) use dummy arguments like this,
   I expect them to be undefined and unusable upon return.  The
   problem in my code was that this undefined nature was propagated
   into the returned values of arguments that I did want.

I wouldn't exactly put it like that.  I'd say the undefinedness had little
or nothing to do with the general problem.  The problem was that your
code, generally speaking, expected a procedure to work even though
invalid aliases were used.  It is an important thing to remember, because
learning the general rule helps you avoid all sorts of problems, only
a few of which involve passing placeholder values for unused arguments.
--





Wed, 16 Aug 1995 20:33:48 GMT  
 
 [ 22 post ]  Go to page: [1] [2]

 Relevant Pages 

1. string.join(["Tk 4.2p2", "Python 1.4", "Win32", "free"], "for")

2. Followup to "Fortran calling "c", and "c" calling Fortran

3. BEGIN{want[]={"s1o", "s2o", "s2q", "s3q"}

4. Parsing ""D""?

5. "Fifth", "Forth", zai nar?

6. Ruby "finalize", "__del__"

7. beginners "let"/"random" question

8. ANNOUNCE: new "plus"- and "dash"-patches available for Tcl7.5a2/Tk4.1a2

9. Looking for "stdin", "stdout"

10. Fortran calling "c", and "c" calling Fortran

11. Fortran calling "c", and "c" calling Fortran

12. Intel "efc" (ITANIUM Fortran) major problems

 

 
Powered by phpBB® Forum Software