13

Occasionally I have heard references to a peculiarity of certain (old) Fortran compilers, with regards to subprogram argument passing. Here is an example, from an answer to a Stack Overflow question:

Some early Fortran compilers implemented constants by using a constant pool. All parameters were passed by reference. If you called a function, e.g.

f(1) 

The compiler would pass the address of the constant 1 in the constant pool to the function. If you assigned a value to the parameter in the function, you would change the value (in this case the value of 1) globally in the program. Caused some head scratching.

The C FAQ also mentions “FORTRAN's classic idiosyncrasy involving constants passed by reference” in a footnote.

I have no problem believing that some compilers behaved this way. However, I would like to have some contemporaneous documents that confirm it. The nature of the document doesn't matter too much; I merely want confirmation from a non-anecdotal source (although some kind of manual would be ideal).

11
  • 5
    This was definitely still a feature of Fortran-77 compilers on PCs in the 1980s. To my knowledge, gfortran still uses this design, except that the constant pool is now mapped to read-only storage, so it is no longer possible to overwrite constants (which could be a source of hard-to-find bugs in the past!). I have searched manuals for old IBM and VMS Fortran compilers for the past 40 minutes, but have been unable to find any language that spells out these inner workings in explicit detail.
    – njuffa
    CommentedMay 23, 2021 at 22:46
  • 1
    DEC Fortran 77 allowed, with some limitations, overriding the default behavior (pass by reference) with %VAL (arg).
    – njuffa
    CommentedMay 23, 2021 at 23:26
  • 1
    IIRC, the IBM Fortran G and H compilers for S/360 and S370 had this feature, but (1) the "constant pool" was local to a subroutine/function, or a least local to a separate complication unit and (2) the exact (undetected but wrong) behavior depended on the optimization level you specified. No idea where if was documented (if anywhere) but it was a well known "programming bug" at the time.
    – alephzero
    CommentedMay 24, 2021 at 0:22
  • 3
    This is a non-issue in modern Fortran (Fortran 90 or later), where you can declare parameter use as "in", "out" or "inout" in an interface block in the calling routine, and the compiler will then check for violations. If you don't declare an interface, the default is "inout" so attempting to use a constant argument is always a compilation error.
    – alephzero
    CommentedMay 24, 2021 at 11:07
  • 3
    I distinctly remember a hard-to-catch bug in one of my FORTRAN IV programs on IBM S/360 in the '70s where the constant 1 mysteriously behaved like 2... because constant 1 was passed as an argument to a SUBROUTINE that incremented it. Fond memories...
    – vonbrand
    CommentedMay 25, 2021 at 2:28

6 Answers 6

9

That fact is explicitly mentioned in the (Russian) book Ошибки-ловушки при программировании на фортране, 1987 (Errors and pitfalls in FORTRAN programming), page 88.

One of the puzzles was to make the sequence of operators

 J=1 PRINT 1,J 1 FORMAT(' J = ',I1) 

print 0.

The provided solution was

 PROGRAM TASK CALL ZERO(1) J=1 PRINT 1,J 1 FORMAT(' J = ',I1) STOP END SUBROUTINE ZERO(K) K=0 RETURN END 
    6

    I'm too young to remember really old Fortran compilers, but the behaviour that you described occurs in all current Fortran compilers. It's a core part of the language standard, so we can safely assume that any standard-conforming compilers in the past used to work this way as well.

    Check out this example at https://godbolt.org/z/GKadP9rov (feel free to switch to Intel Fortran if you aren't a fan of gfortran):

    subroutine foo(x) implicit none integer :: x x = 5 end subroutine program test implicit none call foo(1) call bar(1) end program 

    Because all Fortran subroutines by default accept all arguments by reference, the main program does not have any other option than to pass the constant 1 as a reference to a value in memory. The disassembly proves that the same location is used in both calls:

    foo_: … mov DWORD PTR [rax], 5 … .LC0: .long 1 MAIN__: … mov edi, OFFSET FLAT:.LC0 call foo_ mov edi, OFFSET FLAT:.LC0 call bar_ … 

    Of course, the constant will be placed into the read only data section on most modern platforms (except for those lacking an MMU). The write in foo() is thus going to trigger a segmentation fault at runtime.

    13
    • 3
      Even in your grandfather's FORTRAN, the fact that a constant must be passed by reference does not imply that the reference has to be "the one and only" instance of that value. Load the constant into a scratch location (on the stack if you have a stack) and set the argument to point to it.
      – dave
      CommentedMay 26, 2021 at 23:31
    • 4
      And therefore, I regard passing a reference to a writeable "one and only" instance of a constant as a compiler bug - understandable in the context of the times, and who am I to criticize the pioneers, giants that they were, but nevertheless, it's still an implementation decision that does not match language semantics.
      – dave
      CommentedMay 26, 2021 at 23:48
    • 2
      @texdr.aft - kalgol reports "FAIL 001 IN SECT.4.7.5.2." at run time. As should be perfectly obvious :-), this means an invalid use of an expression called by name. 'SECT.4.7.5.2' is a fixed part of this error message, it does not refer to anything in the original program. Compiler module? Design spec? Documentation reference? No idea. Kalgol always was a little opaque.
      – dave
      CommentedMay 28, 2021 at 1:59
    • 2
      @another-dave Section 4.7.5.2 of the Algol 60 report: “A formal parameter which occurs as a left part variable in an assignment statement within the procedure body and which is not called by value can only correspond to an actual parameter which is a variable (special case of expression).” I missed this when looking through the report yesterday, but it nicely answers your original inquiry about whether a constant called by name can be assigned to.
      – texdr.aft
      CommentedMay 28, 2021 at 2:27
    • 2
      @texdr.aft - Oh, duh: it's a reference to the Report. That's actually useful for an error message,
      – dave
      CommentedMay 28, 2021 at 3:00
    5

    In the 1990s, I worked for a UK company (Polyhedron Software) that produced a suite of code analysis and refactoring tools for Fortran, marketed as PlusFort.

    The GXCHK module performed static analysis of Fortran code to look for common errors, including:

    Subprogram argument mismatch or misuse (e.g. constant actual argument is illegally modified by subprogram).

    Though I am no longer in contact with the company, I can remember this being an issue with Fortran-77 and before, as evidenced by the need of a QA tool to identify such bugs.

      4

      Using an emulated IBM 1130 running DM2, I can confirm Leo B.'s example. It took a little modification to run on the 1130:

      // JOB // FOR *LIST SOURCE PROGRAM *ONE WORD INTEGERS SUBROUTINE ZERO(K) K = 0 RETURN END // DUP *DELETE ZERO *STORE WS UA ZERO // FOR *LIST ALL *IOCS(1132 PRINTER) *ONE WORD INTEGERS J = 1 WRITE(3, 2) WRITE(3, 1) J CALL ZERO(1) J = 1 WRITE(3, 3) WRITE(3, 1) J 1 FORMAT(' J = ', I1) 2 FORMAT(' BEFORE -') 3 FORMAT(' AFTER -') CALL EXIT END // XEQ 

      The output of the program is:

      BEFORE - J = 1 AFTER - J = 0 

      Since the value of J is only ever set to 1, it's clear that the program has set the value of 1 to 0 … 🤔

      4
      • You're restoring the value of 1 at the end of IMDFY. Please try the code in my answer (omitting the PROGRAM header and changing PRINT to WRITE, if necessary).
        – Leo B.
        CommentedMay 26, 2021 at 22:26
      • Thanks, @LeoB.! Your program, modified slightly, produced the expected if odd) result. I'll rewrite the answer
        – scruss
        CommentedMay 26, 2021 at 23:00
      • 4
        I do note that one Jimi Hendrix was aware of this, but evolved programming techniques to avoid problems: "if six turned out the be nine, I don't mind".
        – dave
        CommentedMay 28, 2021 at 11:34
      • I deleted a previous comment - I'd looked at this thread on an inadequate screen and thought the code had literally 3 = 1. Nevermind....
        – dave
        CommentedMay 29, 2021 at 2:18
      1

      This discussion is in support of the previous answers. Hopefully, this is not the answer to some other question that was not asked. The question was in reference to passing values without restriction in old compilers, but no reference was given to how old. So lets start at the time of really old. This appears to be a non-standard feature used in Univac Fortran V (ca. 1973), and and also in Univac ASCII Fortran (ca. 1982). The implementation of Univac Fortran V on the 1100 mainframe system, is the predecessor of Univac ASCII Fortran, and predominantly set the standard for Fortran 77. Univac ASCII Fortran was the full version of Fortran 77 implemented on Univac 1108 system.

      The feature mentioned in the question is very similar to the DEFINE statement in Univac Fortran V. Lets think a minute... Here we are writing a main program and several subprograms called by the main program. The nature of features like DEFINE was to allow control at the head of the main program, before stepping into execution, to set the values or name references of constants, variables and functions that were seen by the executable area of the main program and its subprograms. There were conflict restrictions the programmer had to consider in selecting variable naming conventions for the main program and subprograms, but DEFINE voided that restriction, but only for those constants, variables, and functions set by DEFINE. This gave the ability of the programmer to have program global control of DEFINE features to all areas of the program without passing arguments. Constants, variables, and functions set by DEFINE had unrestricted naming conventions. This was, at times, indispensable. This meant the programmer could set the value of a constant or variable without restriction, or DEFINE a function this way, and could access these defined features anywhere in the executable's environment. These features were defined after setting the type but before the first executable in the main program. The highly proscriptive programming, compilation, and execution environment on the Univac system prevented the modification of memory areas outside of that allocated to the program by the compiler and executive operating system, a protective memory addressing issue. An attempted write-protection violation was a very serious issue. DEFINE allowed a modification of this but only after the non-executable type-setting area of the program, before the executable section of the program. This is consistent with the stated address passing action mentioned in the question.

      Here is an example -

       DEFINE F(X,Y) = X + Y a type definition of a function 

      followed by

       A = F(U*V,V) results in A = U*V + V where A,U,V are *real* 

      or, for example

       D = F(I*J,K*I) I,J,K are double precision, D is double precision by propagation 

      resulting in

       D = I*J + K*I a double precision expression... 

      (Pretty cool! Especially if one does not have to mess with naming restrictions!!)

      Univac Fortran V documentation states the following...

      4.2 DEFINE PROCEEDURES
      A DEFINE generates inline code when it is referenced. Thus, it is analogous to an 1108 assembly procedure, rather than to an external or internal FUNCTION, which generates a closed subprogram. The overhead of a subprogram is eliminated and the optimization capabilities of the compiler are allowed to operate. The FORTRAN IV arithmetic statement functions are a subset of the DEFINE procedure.

      Univac ASCII Fortran documentation shows the definition is little changed and gives the following characterization...

      The statement function generates inline code when it is referenced. This allows efficient references to the defining expression without referencing the expression each time.

      In other words, DEFINE(expression) is a statement function definition that is global (i.e. unrestricted) within the allocated program environment. This would be extremely useful.

      Examination of documentation for two other versions of Fortran 77 shows this feature is recognized as a Fortran 77 feature in Lahey Fortran, 1994, and also recognized in Microsoft Fortran 5.0/5.1 1991, the predecessor of Compaq Fortran mentioned below. The Lahey unavailability may likely be due to memory protection issues necessary for the operating-system environments of non-mainframe systems.

      Examination of documentation for Compaq Fortran version 6.6A (May 2002), the industry standard pc-implementation of VAX Fortran 77 at the time, showed the feature is recognized as non-standard and operates in very much the same way mentioned in the question.

      The following is noted -

      15.1.5 DEFINE and UNDEFINE Directives -
      The DEFINE directive creates a symbolic variable whose existence or value can be tested during conditional compilation. The UNDEFINE directive removes a defined symbol.
      ...
      Rules and Behavior -
      DEFINE and UNDEFINE create and remove variables for use with the IF (or IF DEFINED) directive. Symbols defined with the DEFINE directive are local to the directive. They cannot be declared in the Fortran Program.
      Because Fortran programs cannot access the named variables, the names can duplicate Fortran keywords, intrinsic functions, or user-defined names without conflict. To test whether a symbol has been defined, use the IF DEFINED (name) directive. You can assign an integer value to a defined symbol. To test the assigned value of name, use the IF directive. IF test expressions can contain most logical and arithmetic operators.
      Attempting to undefine a symbol that has not be defined produces a compiler warning.
      The DEFINE and UNDEFINE directives can appear anywhere in a program enabling and disabling symbol definitions.
      Examples - Consider the following

       |DEC$ DEFINE testflag |DEC$ IF DEFINED (testflag) WRITE( * , * ) 'Compiling first line' etc... 

      In the above example, IF DEFINED (testflag) is true, Compiling first line will be written.

      The following documentation may be of interest...

      Univac 1100 Series Fortran V Programmer Reference, UP-4060 Rev. 2, 1973

      Sperry Univac Series 1100 Fortran (ASCII) Level 10-R1 Programmer Reference, UP-8244.2, 1982

      Compaq Fortran Language Reference Manual, Order Number: AA-Q66SD-TK, September 1999

      Compaq Fortran is now Intel Fortran.

      So we went from really old, to maybe not so old. Hope this answer was of some help...

        -4

        Here is an explicit example of the behavior mentioned by texdr.aft in their posted question...

         Program testzog !DEC$ DEFINE flag=3 c ...... executable statements and some overly technical mumbo-jumbo call zog end subroutine zog !DEC$ IF (flag .eq. 3) WRITE (*,*) "This is compiled if flag equals 3." !DEC$ ELSEIF (flag .ge. 4) WRITE (*,*) "Or this compiled if flag greater than or equal to 4." !DEC$ ELSE WRITE (*,*) "Or this compiled if all preceding & conditions .FALSE." !DEC$ ENDIF END 

        Please note, DEFINE is a directive setting the value of flag equal to 3. Poorly written compilers, or lack of familiarity with the Fortran language constructs, can produce results that are not expected. In this case, however, results are as expected and consistent with the behavior mentioned by the OP in their question. The compiler used was Compaq Fortran version 6.6A.

        For testzog, DEFINE flag=3 is a pre-executable directive setting the value of flag equal to 3. As mentioned previously, a DEFINE directive, if used, must occur after setting the type for variables in the main program. Note that the call to subroutine zog includes no reference to flag as a calling argument passed to zog. Compilation and execution of testzog will result in "This is compiled if flag equals 3" for flag=3 and "Or this compiled if flag greater than or equal to 4" for flag=4. The value of flag determines executable actions in subroutine zog and indicates the value of flag is passed by reference, a compiler optimizing procedure invoked when program testzog and companion subroutine zog were compiled.

        2
        • 2
          This seems to straddle off-topic. The question isn’t about conditional compilation.CommentedMay 27, 2021 at 16:54
        • You have apparently made a number of attempts to edit this and other people’s posts. Please log in to be able to edit your posts directly instead of asking moderators to review your edits.CommentedMay 31, 2021 at 8:44

        You must log in to answer this question.

        Start asking to get answers

        Find the answer to your question by asking.

        Ask question

        Explore related questions

        See similar questions with these tags.