1

I am working with a code originally written in Fortran 77 that makes use of namelists (supported by compiler extension at the time of its writing - this feature only became standard in Fortran 90) for reading input files. The namelist input files have groups of namelist variables in between (multiple) plain text headers and footers (see example.nml). Some groups of namelist variables are only read if certain conditions are met for previously read variables.

When reading all the namelist groups in a file in sequence, executables compiled with gfortran, ifort and nagfor all behave the same and give the expected output. However, when a given namelist group in the input file is to be skipped (the optional reading), gfortran and ifort executables handle this as desired, while the executable compiled with nagfor raises a runtime error:

Runtime Error: reader.f90, line 27: Expected NAMELIST group /GRP3/ but found /GRP2/ Program terminated by I/O error on unit 15 (File="example.nml",Formatted,Sequential)

As a minimal working example reproducing the problem, consider the namelist file example.nml and driver program reader.f90 given below, in which NUM2 from namelist group GRP2 should only be read if NUM1 from namelist group GRP1 equals 1:

example.nml:

this is a header

 &GRP1  NUM1=1     /
 &GRP2  NUM2=2     /
 &GRP3  NUM3=3     /
this is a footer

reader.f90:

program reader

  implicit none
  character(len=40)   :: hdr, ftr
  integer             :: num1, num2, num3, icode

  ! namelist definition
  namelist/grp1/num1
  namelist/grp2/num2
  namelist/grp3/num3

  ! open input file
  open(unit=15, file='example.nml', form='formatted', status='old', iostat=icode)

  ! read input data from namelists
  read(15, '(a)') hdr
  print *, hdr

  read(15, grp1)
  print *, num1

  if (num1 == 1) then
    read(15, grp2)
    print *, num2
  end if

  read(15,grp3)
  print *, num3

  read(15, '(a)') ftr
  print *, ftr

  ! close input file
  close(unit=15)

end program reader

All executables give this expected output when NUM1=1:

 this is a header
           1
           2
           3
 this is a footer

However, when e.g. NUM1=0, the executables compiled with gfortran and ifort give the desired output:

 this is a header
           0
           3
 this is a footer

while the executable compiled with nagfor (which is known for being strictly standard conforming), reads the header and first namelist group:

 this is a header
 0

but then terminates with the previously mentioned runtime error.

As indicated by the error message, example.nml is accessed sequentially, and if that is the case, /GRP2/ is the next record to be read, not /GRP3/ as the program logic asks for, so the error message makes sense to me.

So my question is this:

  1. Can the shown behaviour be attributed to standard (non-)conformance enforced by nagfor and not gfortran and ifort?
  2. If so, does this mean that the non-sequential reading observed with gfortran and ifort is due to extensions supported by these compilers (and not nagfor)? Can this be turned on/off using compiler flags?
  3. The simplest workaround I can think of (minimal change to a large existing program), would be to add a dummy read(15,*) in an else branch for the if statement in reader.f90. This seems to work with all the mentioned compilers. Would this make the code standard conforming (Fortran 90 or later)?

These are the compiler versions and options that were used to compile the executables:

  • GNU Fortran (Ubuntu 9.1.0-2ubuntu2~18.04) 9.1.0: gfortran -Wall -Wextra -fcheck=all -g -Og -fbacktrace reader.f90
  • Intel(R) Visual Fortran, Version 16.0 Build 20160415: ifort -Od -debug:all -check:all -traceback reader.f90
  • NAG Fortran Compiler Release 6.1(Tozai) Build 6116: nagfor -O0 -g -C reader.f90
jbdv
  • 1,263
  • 1
  • 11
  • 18
  • 1
    As I don't use them I can't answer, but I'll just note Fortran77 is irrelevant here - NAMELIST didn't come into the language until Fortran90 – Ian Bush Aug 19 '19 at 10:00
  • @IanBush, thanks for the comment and confirmation. I tried to work into the question that the namelist functionality was supported by a compiler extension in the original code, and that a standard conforming solution would have to be later than 77, for just this reason. But this does mean I should maybe write my example in a more modern dialect.. – jbdv Aug 19 '19 at 10:05

2 Answers2

2

When namelist formatting is requested on an external file, the namelist record is taken to commence at the record at the current position of the file.

The structure of a namelist input record is well defined by the language specification (see Fortran 2018 13.11.3.1, for example). In particular, this does not allow a mismatching namelist group name. nagfor complaining about this does so legitimately.

Several compilers do indeed appear to continue skipping over records until the namelist group is identified in a record, but I'm not aware of compiler flags available to control that behaviour. Historically, the case was generally that multiple namelists would be specified using distinct files.

Coming to your "simple workaround": this is, alas, not sufficient in the general case. Namelist input may consume several records of an external file. read(15,*) will advance the file position by only a single record. You will want to advance to after the terminating record for the namelist.

When you know the namelist is just that single record then the workaround is good.

francescalus
  • 30,576
  • 16
  • 61
  • 96
  • Thank you for the clear explanation. Commencing at the record at the current position in the file, upon requesting a namelist read on an external file, makes sense to me - and as I understand this is then exactly what nagfor does. What I find more difficult to glean from the standard, is that it is then allowed to start at a given record and _continue reading records_ (not comments, or blanks that can legitimately be ignored) until a matching namelist group is found (or setting a status/error code if not). But I suppose this could be inferred from 13.11.3.2 (5)? – jbdv Aug 20 '19 at 07:24
  • 1
    [Another question](https://stackoverflow.com/questions/57558684/does-fortran-have-undefined-behavior) from yesterday is possibly relevant. 13.11.3.1 describes what an input record must look like to have a standard interpretation, and having a non-matching group name means the record isn't valid. However, the standard doesn't say that this must be an error or that the compiler can't keep searching for a new group. – francescalus Aug 20 '19 at 09:12
0

@francescalus' answer and comment on that answer, clearly explained the first two parts of my question, while pointing out a flaw in the third part. In the hope that it may be useful to others that stumble upon a similar problem with a legacy code, here is the workaround I ended up implementing:

In essence, the solution is to ensure that the file record marker is always positioned correctly before attempting any namelist group read. This positioning is done in a subroutine that rewinds an input file, reads through records until it finds one with a matching group name (if not found, an error/warning can be raised) and then rewinds and repositions the file record marker to be ready for a namelist read.

subroutine position_at_nml_group(iunit, nml_group, status)
  integer,          intent(in)  :: iunit
  character(len=*), intent(in)  :: nml_group
  integer,          intent(out) :: status

  character(len=40)  :: file_str
  character(len=:), allocatable :: test_str
  integer :: i, n

  ! rewind file
  rewind(iunit)

  ! define test string, i.e. namelist group we're looking for
  test_str = '&' // trim(adjustl(nml_group))

  ! search for the record containing the namelist group we're looking for
  n = 0
  do
    read(iunit, '(a)', iostat=status) file_str
    if (status /= 0) then
      exit ! e.g. end of file
    else
      if (index(adjustl(file_str), test_str) == 1) then
        ! backspace(iunit) ?
        exit ! i.e. found record we're looking for
      end if
    end if
    n = n + 1 ! increment record counter
  end do

  ! can possibly replace this section with "backspace(iunit)" after a
  ! successful string compare, but not sure that's legal for namelist records
  ! thus, the following:
  if (status == 0) then
    rewind(iunit)
    do i = 1, n
      read(iunit, '(a)')
    end do
  end if

end subroutine position_at_nml_group

Now, before reading any (possibly optional) namelist group, the file is positioned correctly first:

program new_reader
  implicit none
  character(len=40)    :: line
  integer              :: num1, num2, num3, icode

  ! namelist definitions
  namelist/grp1/num1
  namelist/grp2/num2
  namelist/grp3/num3

  ! open input file
  open(unit=15, file='example.nml', access='sequential', &
       form='formatted', status='old', iostat=icode)

  read(15, '(a)') line
  print *, line

  call position_at_nml_group(15, 'GRP1', icode)
  if (icode == 0) then
    read(15, grp1)
    print *, num1
  end if

  if (num1 == 1) then
    call position_at_nml_group(15, 'GRP2', icode)
    if (icode == 0) then
      read(15, grp2)
      print *, num2
    end if
  end if

  call position_at_nml_group(15, 'GRP3', icode)
  if (icode == 0) then
    read(15, grp3)
    print *, num3
  end if

  read(15, '(a)') line
  print *, line

  ! close input file
  close(unit=15)

contains

  include 'position_at_nml_group.f90'

end program new_reader

Using this approach eliminates uncertainty in how different compilers treat not finding matching namelist groups at the current record in a file, generating the desired output for all compilers tested (nagfor, gfortran, ifort).

Note: For brevity, only a bare minimum of error checking is done in the code snippet shown here, this (and case insensitive string comparison!) should probably be added.

jbdv
  • 1,263
  • 1
  • 11
  • 18