Writing non-questionable Fortran (Part 1)

Stories from Victor Azizi

Jan 29, 2022

Hello World! This is my very first blog about Fortran, I hope you enjoy the read!

Fortran is a programming language that has been around since the 1957s, measured in years it is at least twice as old as me! Fortran has been the language of choice for many HPC applications, especially in the fysics related domains, but even as part of scipy! During its lifetime standards for writing Fortran have changed. And even the format for writing fortran has changed. 5 standards of Fortran exist, listed from old to young:

And 2 distinct formats for writing Fortran exist:

It is important to note that all standards of Fortran can be written in any format, thus the fixed form format is not missing any functionality, it just can be a pain in the ass to format the code correctly. The two formats are usually seperated by the file extension, .f for fixed form, and .f90 for non fixed format

Fixing Implicit Hello World

So you have decided you want to write some Fortran code? Great! Fortran still has its uses, especially in academia and research, and has some interesting properties for writing high performant code (e.g. subslicing of arrays and performance oriented aliasing assumptions). However Fortran also has many pitfalls which are made worse by all the (scarcely known) legacy features that have to be supported.

And that brings us to the topic of this blog: How the hell do you write sane, non-questionable Fortran nowadays?!. For me the answer begins with making sure that no implicit behaviour is allowed by the compiler, meaning that the compiler can decide on the type of variables and signatures of procedures. Because Fortran does this in a very unintuitive way (for example, for variables it looks at the first letter of the variable to determine the type, what?! 😡). But first lets look at this example code:

gfortran has no problem compiling these files without warnings to create an executable, but if we compile and run this executable we do not get the output we expect:

> gfortran main.f90 hello_world.f90 -o hello_world
> ./hello_world
Hello World    544567129

(We were expecting Hello World: You there right?)

What happens here is that Fortran can implicitly decide what functions and variables look like, for this example this goes wrong in 2 places:

1. The type of name is not specified in hello_world.f90. Therefore Fortran looks at the first letter of the variable (in this case n) and decides that therefore it implicitly has to be an integer, not at all what we wanted!

2. The call to the hello_world procedure in main.f90 is implicit, which means that the compiler has no information about what the actual procedure looks like, and decides on the spot what it should look like. In this case it would decide that the procedure has the following signature:

subroutine hello_world(name : character(len=*))

Which is different from the actual signature of the hello_world procedure that we have compiled, in that procedure name has the type integer!

Luckily gfortran (I'll stick to gfortran, although many commercial/non-open compilers have similar flags, but named differently) has compiler flags available that disallow such legacy behaviour of Fortran. The following 2 compiler flags should solve the issues that we are having with the example.

If we add implicit none to our program and procedures, and compile with the flags enabled the following happens:

> gfortran -fimplicit-none -Werror=implicit-interface main.f90 hello_world.f90 -o hello_world
main.f90:2:31:

  2 |   call hello_world("You there")
    |                               1
Error: Procedure β€˜hello_world’ called with an implicit interface at (1) [-Werror=implicit-interface]
f951: some warnings being treated as errors
hello_world.f90:1:27:

  1 | subroutine hello_world(name)
    |                           1
Error: Symbol β€˜name’ at (1) has no IMPLICIT type

Great! The compiler is trying to tell us what went wrong. Lets solve the first error first. We have called the Procedure hello_world with an implicit interface, uh-oh 😨

The interface of the hello_world function can be exposed to the main.f90 in three ways:

  1. Including the hello_world.f90 file in the main.f90 file

  1. Telling the compiler explicitly what the interface looks like and adding it to the program

  1. Creating a module from the hello_world.f90 file, and telling main.f90 to use the hello_world definition from there

Method 1 causes the subroutine to be duplicated each time it is included, probably not ideal. Method 2 gets rid of the errors, but the interface is still not correct! Method 3 does not cause code duplication, and is not able to wrongly specify the interface. Therefore my preference always goes to method 3. However, now we have introduced an ordering in which the files must be compiled. There are a few programs out in the wild which can create the correct compile order for you (e.g. CMake and makedepf90 ). But for now we know the correct order, first we have to compile hello_world.f90 to get the interface hello_world_mod.mod and object file hello_world.o:

# We will come back to the -fimplicit-none later
> gfortran -c -Werror=implicit-interface hello_world.f90
> gfortran -Werror=implicit-interface main.f90 -o hello_world
main.f90:3:31:

  3 |   call hello_world("You there")
    |                               1
Error: Type mismatch in argument β€˜name’ at (1); passed CHARACTER(1) to INTEGER(4)

Aw shucks, We still have an error left! gfortran tells us that we are trying to use a CHARACTER(1) in a function that takes an INTEGER(4). Which brings us to the second problem we have to solve: the types used in our program!

Recompile everything, et voìla:

# create hello_world_mod.mod and hello_world.o
> gfortran -c -fimplicit-none -Werror=implicit-interface hello_world.f90
# create main.o
> gfortran -c -fimplicit-none -Werror=implicit-interface main.f90
# link and create hello_world binary
> gfortran main.o hello_world.o -o hello_world
./hello_world
 Hello World: You there

We have successfully fixed our program and made all the interfaces and variables explicit, leaving less room for questionable-Fortran behaviour.

Takeaway

Always use the -fimplicit-none and -Werror=implicit-interface compiler options when compiling your Fortran code, and add implicit none to every procedure and the program itself!