Intrinsic Modules and iso_fortran_env

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

This page is written against Fortran 2023 (ISO/IEC 1539-1:2023, J3/23-007r1, cited as "S" with clause numbers) and was checked on 2026-10-19. Every example was compiled and run with GNU Fortran 16.2 under gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all and, wherever the feature exists there, with LLVM Flang 23, on an aarch64 Linux container. The output shown is real output. Where the two compilers print different values, which is the normal case for kind numbers and status codes, both are shown. Examples that need a library (OpenMP, MPI, coarrays) are compiled with the flags named in the text, and examples that are deliberately wrong show the real diagnostics. Intel ifx, NAG and Cray compilers were not available, so nothing below claims how they behave.

An intrinsic module is a module that the processor provides, in the way a processor provides intrinsic procedures, but whose entities have to be imported with a use statement. Clause 16.10 of the standard defines five of them: iso_fortran_env, which describes the Fortran environment, ieee_exceptions, ieee_arithmetic and ieee_features, which give access to IEEE floating-point behaviour, and iso_c_binding, which supports interoperability with C. Of the five, iso_fortran_env is the one almost every program imports, because it is the portable way to name kinds (int64, real64) and units (output_unit, error_unit). The running example, heatflow, imports it in heatflow_kinds, and this page adds a small environment module for logging. Kinds are explained in Intrinsic Types and Kinds, the numeric inquiry functions in Numeric Model, Bit Manipulation and Conversion, units and files in Input/Output: Units and Files, error stops in Error Handling and Program Termination, IEEE arithmetic in Floating-Point and IEEE Arithmetic, C interoperability in Interoperability with C, and images, locks, events and teams in Coarrays, Images and Synchronization and Coarray Teams, Events, Collectives and Atomics.

The five standard intrinsic modules

Module Purpose Defined in

iso_fortran_env

kind constants and kind arrays, preconnected unit numbers, storage sizes, iostat and stat values, compiler_version, the lock, event, team and notify types

S 16.10.2

iso_c_binding

kind constants for C types, c_ptr and c_funptr, c_loc, c_f_pointer, c_sizeof, c_null_char

S 18.2

ieee_exceptions

floating-point flags and halting modes: ieee_overflow, ieee_get_flag, ieee_set_halting_mode

S 17

ieee_arithmetic

ieee_value, ieee_is_nan, rounding and underflow modes, ieee_support_*; behaves as if it contained use ieee_exceptions

S 17

ieee_features

named constants such as ieee_datatype, ieee_halting that a program may import to request IEEE support

S 17

Three properties of these modules matter in practice.

  • Their entities are not intrinsic. The standard says so explicitly (S 16.10.1, Note 1): int64 is an entity of a module, kind is an intrinsic function. A name from a module can be renamed, restricted with only and hidden with private; an intrinsic procedure name cannot be imported and does not need to be.

  • The processor may add entities. S 16.10.1 allows a processor to extend a standard intrinsic module with public entities that the standard does not specify. That is why use, intrinsic :: iso_fortran_env without only is a portability risk: a vendor extension can clash with one of your names (see Naming clashes and unqualified use).

  • The procedures of iso_fortran_env are simple. The module procedures of 16.10.2 (compiler_version, compiler_options) are simple procedures (S 16.10.1), so they can be called from pure procedures and from do concurrent.

The standard makes support for the IEEE modules processor dependent (S 17.1: whether the modules are provided is processor dependent), while iso_fortran_env and iso_c_binding are required.

Selecting an intrinsic module with use, intrinsic

The use statement is USE [ [ , module-nature ] :: ] module-name (S 14.2.2, R1409), where the module nature is intrinsic or non_intrinsic (R1410). The constraints that matter are:

  • C1404: with intrinsic the name shall be an intrinsic module, and C1405: with non_intrinsic it shall be a nonintrinsic one.

  • C1406: one scoping unit shall not directly reference an intrinsic and a nonintrinsic module of the same name.

  • With no nature, the standard says that if the name is the name of both an intrinsic and a nonintrinsic module, the nonintrinsic module is accessed (S 14.2.2).

The last rule is why the nature should always be written. If a project has a file defining module iso_fortran_env (a "compatibility" shim from the Fortran 90 days is the usual origin), a bare use iso_fortran_env silently picks the shim and the program gets different kinds. use, intrinsic :: iso_fortran_env cannot be hijacked in that way. Modules, submodules and the nature rules are explained with a working example in Modules and Submodules.

use, intrinsic :: iso_fortran_env, only: int64, real64, error_unit

The recommendation this page follows, and the one the Fortran community’s best-practice guide makes, is to write all three parts: the nature, the module name and an only list. The only list documents what the unit needs, keeps the names out of the way of local names, and makes unused imports visible to a reader and to a linter.

Asking for something that is not an intrinsic module is an error with a clear message. A misspelled name:

program typo
  use, intrinsic :: iso_fortran_envs, only: int32
  implicit none
  print *, int32
end program typo

gfortran 16.2 (the real diagnostic):

27_typo.f90:2:20:

    2 |   use, intrinsic :: iso_fortran_envs, only: int32
      |                    1
Fatal Error: Cannot find an intrinsic module named 'iso_fortran_envs' at (1)
compilation terminated.

Flang 23 does not distinguish the two cases: it looks for a module file and reports that it cannot find one.

error: Semantic errors in 27_typo.f90
27_typo.f90:2:21: error: Cannot parse module file for module 'iso_fortran_envs': Source file 'iso_fortran_envs.mod' was not found

And asking for the nonintrinsic version of an intrinsic module fails for the opposite reason: no module file of that name exists on the machine. With use, non_intrinsic :: iso_fortran_env, only: int32 (file 23_nonintrinsic_env.f90, a four-line program) gfortran 16.2 reports:

23_nonintrinsic_env.f90:2:24:

    2 |   use, non_intrinsic :: iso_fortran_env, only: int32
      |                        1
Fatal Error: Cannot open module file 'iso_fortran_env.mod' for reading at (1): No such file or directory
compilation terminated.

and Flang 23 reports that it cannot parse the module file iso_fortran_env.mod.

iso_fortran_env

The entities of iso_fortran_env grouped into kinds, units and storage sizes, input/output status codes, image status codes, derived types and teams, and inquiry functions

Every entity of iso_fortran_env is either a default-integer named constant, a derived type, or a transformational function (S 16.10.2.1). The module is the portable replacement for non-standard kind notations such as real*8 and integer*4, and for hard-coded unit numbers.

Kind constants

int8, int16, int32 and int64 (S 16.10.2.14) are the kind type parameters of integers whose storage size is 8, 16, 32 and 64 bits. real16, real32, real64 and real128 (S 16.10.2.27) do the same for reals, and logical8, logical16, logical32 and logical64 (S 16.10.2.21) for logicals. The rules are the same for all three families:

  • The value is the kind of a type whose storage size in bits is the number in the name. The name says nothing about IEEE conformance, only about size: real64 is a 64-bit real, which on every mainstream processor is IEEE binary64, but the standard does not promise it.

  • If the processor has several kinds of that size, which one is provided is processor dependent.

  • If the processor has no kind of that size, the constant is -2 when the processor has a larger kind and -1 otherwise. A negative constant is therefore a legal value, not an error, and real(kind=real16) with real16 equal to -2 is a compile-time error at the point of use.

program kind_constants
  use, intrinsic :: iso_fortran_env, only: int8, int16, int32, int64, &
                                           real16, real32, real64, real128, &
                                           logical8, logical16, logical32, logical64
  implicit none
  print '(a,4(1x,i0))',  'integer kinds int8..int64:      ', int8, int16, int32, int64
  print '(a,4(1x,i0))',  'real kinds real16..real128:     ', real16, real32, real64, real128
  print '(a,4(1x,i0))',  'logical kinds logical8..64:     ', logical8, logical16, logical32, logical64
  print '(a,i0,a,i0)',   'kind(1) = ', kind(1), ', kind(1.0) = ', kind(1.0)
  print '(a,i0,a,i0)',   'kind(1.0d0) = ', kind(1.0d0), ', kind(.true.) = ', kind(.true.)
end program kind_constants

gfortran 16.2:

integer kinds int8..int64:       1 2 4 8
real kinds real16..real128:      -2 4 8 16
logical kinds logical8..64:      1 2 4 8
kind(1) = 4, kind(1.0) = 4
kind(1.0d0) = 8, kind(.true.) = 4

Flang 23 supports half precision, so its real16 is a real kind:

integer kinds int8..int64:       1 2 4 8
real kinds real16..real128:      2 4 8 16
logical kinds logical8..64:      1 2 4 8
kind(1) = 4, kind(1.0) = 4
kind(1.0d0) = 8, kind(.true.) = 4

On these two compilers the kind numbers equal the number of bytes, but that is a convention of the compilers and not of the language: other a compiler is free to number kinds differently (the standard only says that the numbers are processor dependent), which is exactly why a program should write real64, a named constant from a kinds module, or selected_real_kind, and never a literal 8. The comparison between the two compilers above is also a reminder that real16 is new: F2023 added real16, logical8, logical16, logical32 and logical64 to the module (S, Introduction, "Intrinsic modules"); int8 to int64 and real32 to real128 come from Fortran 2008.

Kind constants versus selected_real_kind

selected_real_kind(p, r) asks for a precision and a range and answers with a kind that satisfies both, or a negative number. The kind constants ask for a size. They are not the same request: a processor can have a 64-bit real that is not IEEE double, or a 80-bit extended type stored in 128 bits. Practical guidance:

  • Use real64 (or the project kind dp) when the data must interoperate with C double, files written by other programs, or MPI types: the layout matters, not the precision.

  • Use selected_real_kind when the algorithm needs a precision, for example "at least 15 digits".

  • Never write real*8 or kind=8: the former is not standard Fortran, the latter is not portable.

A module that hides the choice, and degrades gracefully when a kind is missing, uses merge in a constant expression, because the negative constants make the comparison reliable:

module demo_kinds
  use, intrinsic :: iso_fortran_env, only: real16, real32, real64, real128
  implicit none
  private
  public :: sp, dp, hp, qp
  integer, parameter :: sp = real32
  integer, parameter :: dp = real64
  ! A negative constant means "no such kind": fall back instead of failing to compile.
  integer, parameter :: hp = merge(real16, sp, real16 > 0)
  integer, parameter :: qp = merge(real128, dp, real128 > 0)
end module demo_kinds

program defensive_kinds
  use demo_kinds, only: sp, dp, hp, qp
  implicit none
  print '(a,i0,a,i0,a,i0)', 'hp: kind=', hp, ' precision=', precision(1.0_hp), ' range=', range(1.0_hp)
  print '(a,i0,a,i0,a,i0)', 'sp: kind=', sp, ' precision=', precision(1.0_sp), ' range=', range(1.0_sp)
  print '(a,i0,a,i0,a,i0)', 'dp: kind=', dp, ' precision=', precision(1.0_dp), ' range=', range(1.0_dp)
  print '(a,i0,a,i0,a,i0)', 'qp: kind=', qp, ' precision=', precision(1.0_qp), ' range=', range(1.0_qp)
  print '(a,i0)', 'selected_real_kind(15) = ', selected_real_kind(15)
  print '(a,i0)', 'selected_real_kind(33) = ', selected_real_kind(33)
  print '(a,i0)', 'selected_real_kind(40) = ', selected_real_kind(40)
end program defensive_kinds

gfortran 16.2:

hp: kind=4 precision=6 range=37
sp: kind=4 precision=6 range=37
dp: kind=8 precision=15 range=307
qp: kind=16 precision=33 range=4931
selected_real_kind(15) = 8
selected_real_kind(33) = 16
selected_real_kind(40) = -1

On Flang 23 the first line is hp: kind=2 precision=3 range=4, the others are identical. selected_real_kind(40) is -1 on both, which means "no kind has that precision" (-2 would mean "not that range", -3 neither). The generic mechanism, including selected_int_kind and selected_logical_kind, is on Intrinsic Types and Kinds.

Kind arrays

integer_kinds, real_kinds, logical_kinds and character_kinds (S 16.10.2.15, 16.10.2.26, 16.10.2.20, 16.10.2.4) are rank-one default-integer array constants with lower bound 1 whose elements are all the kinds the processor supports. The order is processor dependent, so a program must not assume that the elements are sorted. They answer questions that int8 and friends cannot: "which kinds exist?", "how many?", "is kind 3 supported?".

program kind_arrays
  use, intrinsic :: iso_fortran_env, only: integer_kinds, real_kinds, logical_kinds, character_kinds
  implicit none
  integer :: i
  print '(a,i0,a,*(1x,i0))', 'integer_kinds (', size(integer_kinds), '):', integer_kinds
  print '(a,i0,a,*(1x,i0))', 'real_kinds    (', size(real_kinds), '):', real_kinds
  print '(a,i0,a,*(1x,i0))', 'logical_kinds (', size(logical_kinds), '):', logical_kinds
  print '(a,i0,a,*(1x,i0))', 'character_kinds (', size(character_kinds), '):', character_kinds
  print '(a)', 'kind  digits  huge'
  do i = 1, size(integer_kinds)
    select case (integer_kinds(i))
    case (1); print '(i4,2x,i6,2x,i0)', 1, digits(1_1), huge(1_1)
    case (2); print '(i4,2x,i6,2x,i0)', 2, digits(1_2), huge(1_2)
    case (4); print '(i4,2x,i6,2x,i0)', 4, digits(1_4), huge(1_4)
    case (8); print '(i4,2x,i6,2x,i0)', 8, digits(1_8), huge(1_8)
    case (16); print '(i4,2x,i6,2x,a)', 16, digits(1_16), 'a very large value'
    end select
  end do
end program kind_arrays

gfortran 16.2:

integer_kinds (5): 1 2 4 8 16
real_kinds    (3): 4 8 16
logical_kinds (5): 1 2 4 8 16
character_kinds (2): 1 4
kind  digits  huge
   1       7  127
   2      15  32767
   4      31  2147483647
   8      63  9223372036854775807
  16     127  a very large value

Flang 23 reports the same integer kinds but different sets for the others:

integer_kinds (5): 1 2 4 8 16
real_kinds    (5): 2 3 4 8 16
logical_kinds (4): 1 2 4 8
character_kinds (3): 1 2 4

The differences are genuine: Flang offers a half-precision real (kind 2) and a bfloat16 real (kind 3), and wider character kinds 2 and 4 (kind 4 is the one selected_char_kind('ISO_10646') returns), while gfortran offers kinds 1 and 4 only; gfortran has a 16-byte logical, Flang does not. (The three kinds in gfortran’s real_kinds are the 4-, 8- and 16-byte reals; this aarch64 build has no 80-bit extended type, and x86-64 builds of gfortran typically list a kind 10 in addition.) The arrays are named constants but they list kinds that vary by compiler, so they are mostly used in tests, in select case dispatch, in table-driven code and for diagnostics (an is_supported(kind) helper is one line with any).

Storage sizes

character_storage_size, numeric_storage_size and file_storage_size (S 16.10.2.5, 16.10.2.23, 16.10.2.11) give, in bits, the size of the character storage unit, of the numeric storage unit (the size of a default integer, default real and default logical) and of the file storage unit (what inquire(iolength=) and recl= count). The numeric storage unit is the one that matters for equivalence, common and sequence types (S 19.5.3.2), and the file storage unit is what makes recl= and unformatted stream sizes portable.

program storage_sizes
  use, intrinsic :: iso_fortran_env, only: character_storage_size, numeric_storage_size, &
                                           file_storage_size
  implicit none
  integer :: i
  real :: x
  character(len=1) :: c
  print '(a,i0)', 'character_storage_size = ', character_storage_size
  print '(a,i0)', 'numeric_storage_size   = ', numeric_storage_size
  print '(a,i0)', 'file_storage_size      = ', file_storage_size
  print '(a,i0)', 'storage_size(i)        = ', storage_size(i)
  print '(a,i0)', 'storage_size(x)        = ', storage_size(x)
  print '(a,i0)', 'storage_size(c)        = ', storage_size(c)
end program storage_sizes
character_storage_size = 8
numeric_storage_size   = 32
file_storage_size      = 8
storage_size(i)        = 32
storage_size(x)        = 32
storage_size(c)        = 8

Both compilers print this. The intrinsic storage_size returns the size of one element of its argument in bits, and for a default integer it equals numeric_storage_size. Do not use character_storage_size to compute the size of a record of text that you intend to write to a file: use inquire(iolength=n) item instead, which answers in file storage units.

Preconnected units

input_unit, output_unit and error_unit (S 16.10.2.13, 16.10.2.24, 16.10.2.9) identify the preconnected units. input_unit is the unit that a read with no unit and the asterisk unit * in a read denote, output_unit is the asterisk unit of write and the unit of print, and error_unit is the unit "used for the purpose of error reporting"; it may be the same as output_unit. The standard fixes the roles but not the numbers, except that none of them is -1. The common values are 5, 6 and 0.

program units
  use, intrinsic :: iso_fortran_env, only: input_unit, output_unit, error_unit
  implicit none
  integer, parameter :: us(3) = [input_unit, output_unit, error_unit]
  character(len=*), parameter :: names(3) = [character(len=11) :: 'input_unit', 'output_unit', 'error_unit']
  character(len=10) :: act, frm, acc
  logical :: is_open
  integer :: i
  print '(a,3(1x,i0))', 'input_unit, output_unit, error_unit =', input_unit, output_unit, error_unit
  write (output_unit, '(a)') 'to the standard output (output_unit)'
  write (error_unit, '(a)') 'to the standard error (error_unit)'
  write (*, '(a)') 'to the unit written as an asterisk'
  do i = 1, size(us)
    inquire (unit=us(i), opened=is_open, action=act, form=frm, access=acc)
    print '(a11,a,l1,3(a,a))', names(i), ' opened=', is_open, &
      ' action=', trim(act), ' form=', trim(frm), ' access=', trim(acc)
  end do
end program units

Standard output (identical on gfortran 16.2 and Flang 23):

input_unit, output_unit, error_unit = 5 6 0
to the standard output (output_unit)
to the unit written as an asterisk
input_unit  opened=T action=READ form=FORMATTED access=SEQUENTIAL
output_unit opened=T action=WRITE form=FORMATTED access=SEQUENTIAL
error_unit  opened=T action=WRITE form=FORMATTED access=SEQUENTIAL

Standard error:

to the standard error (error_unit)

Notes:

  • The unit named error_unit is a separate stream from output_unit on both compilers, so a shell redirect > out.txt leaves the error message on the terminal. The two streams are not ordered with respect to each other: when both go to one terminal, the order of the lines depends on buffering. If the order matters, flush(output_unit) before writing to error_unit.

  • input_unit is preconnected for sequential formatted input on image one of the initial team only; on other images it is not preconnected (S 16.10.2.13). In a coarray program, read the input on image one and distribute it.

  • Because input_unit, output_unit and error_unit are named constants, a program cannot declare a variable with one of those names after importing them. Likewise do not hard-code 5, 6 or 0: a processor that numbers its units differently would break the program, and open(unit=6, ...) on a preconnected unit has processor-dependent behaviour.

  • Choosing free unit numbers: use open(newunit=u, ...) (Fortran 2008) as in example 25_heatflow_env.f90 below, which cannot collide with the preconnected units. See Input/Output: Units and Files.

Input/output status codes

iostat_end, iostat_eor and iostat_inquire_internal_unit (S 16.10.2.16, 16.10.2.17, 16.10.2.18) are the values assigned to the variable of an iostat= specifier. iostat_end is assigned when an end-of-file condition occurs and no error occurs; iostat_eor is assigned for an end-of-record condition (as in the non-advancing read below) when neither end-of-file nor an error occurs. Both are negative, they are different, and a successful statement returns zero. The consequence is a test that does not depend on any compiler-specific value:

program iostat_codes
  use, intrinsic :: iso_fortran_env, only: iostat_end, iostat_eor, iostat_inquire_internal_unit, &
                                           output_unit
  implicit none
  character(len=5) :: text = '10 20'
  character(len=4) :: word
  integer :: a, b, c, ios, n
  print '(a,i0,a,i0,a,i0)', 'iostat_end=', iostat_end, ' iostat_eor=', iostat_eor, &
    ' iostat_inquire_internal_unit=', iostat_inquire_internal_unit
  ! End of file: reading a third item from a two-item internal record.
  read (text, *, iostat=ios) a, b, c
  print '(a,i0,a,l1)', 'list-directed read, three items from two: iostat=', ios, ' is end: ', ios == iostat_end
  ! End of record: a non-advancing read of a short record with a long format item.
  open (unit=10, status='scratch', action='readwrite')
  write (10, '(a)') 'abc'
  rewind (10)
  read (10, '(a4)', advance='no', iostat=ios, size=n) word
  print '(a,i0,a,l1,a,i0)', 'non-advancing read of a short record: iostat=', ios, &
    ' is eor: ', ios == iostat_eor, ' characters read=', n
  close (10)
  ! Typical loop: end of file is a state, not an error.
  open (unit=11, status='scratch', action='readwrite')
  write (11, '(i0)') 1, 2, 3
  rewind (11)
  n = 0
  do
    read (11, *, iostat=ios) a
    if (ios == iostat_end) exit
    if (ios /= 0) stop 'read error'
    n = n + a
  end do
  print '(a,i0)', 'sum read until iostat_end: ', n
  close (11)
  flush (output_unit)
end program iostat_codes

gfortran 16.2:

iostat_end=-1 iostat_eor=-2 iostat_inquire_internal_unit=5018
list-directed read, three items from two: iostat=-1 is end: T
non-advancing read of a short record: iostat=-2 is eor: T characters read=3
sum read until iostat_end: 6

Flang 23 prints the same lines except iostat_inquire_internal_unit=256. iostat_end and iostat_eor are -1 and -2 on both compilers, but neither number is required by the standard; positive values mean an error and their values are processor dependent. Never compare ios to a literal -1: write ios == iostat_end.

iostat_inquire_internal_unit is the value an inquire statement assigns to its iostat= variable when the unit it names is an internal unit. It only occurs inside a defined derived-type input/output procedure that was called while the parent statement was reading or writing an internal file, because that procedure receives the internal file as its unit argument. A DTIO routine that wants to behave differently for files and strings can therefore ask:

module probe_mod
  use, intrinsic :: iso_fortran_env, only: iostat_inquire_internal_unit
  implicit none
  type :: probe_t
    integer :: v = 0
  contains
    procedure :: write_probe
    generic :: write(formatted) => write_probe
  end type probe_t
contains
  subroutine write_probe(dtv, unit, iotype, v_list, iostat, iomsg)
    class(probe_t), intent(in) :: dtv
    integer, intent(in) :: unit
    character(len=*), intent(in) :: iotype
    integer, intent(in) :: v_list(:)
    integer, intent(out) :: iostat
    character(len=*), intent(inout) :: iomsg
    integer :: ios
    character(len=16) :: nm
    inquire (unit=unit, form=nm, iostat=ios)
    if (ios == iostat_inquire_internal_unit) then
      write (unit, '(a,i0)', iostat=iostat, iomsg=iomsg) 'internal:', dtv%v
    else
      write (unit, '(a,i0)', iostat=iostat, iomsg=iomsg) 'external:', dtv%v
    end if
    if (size(v_list) < 0 .or. len(iotype) < 0) iostat = 1
  end subroutine write_probe
end module probe_mod

program inquire_internal
  use probe_mod, only: probe_t
  implicit none
  type(probe_t) :: p
  character(len=20) :: buf
  p%v = 7
  write (buf, '(dt)') p
  print '(a)', trim(buf)
  write (*, '(dt)') p
end program inquire_internal
internal:7
external:7

Both compilers print this. Observed on gfortran 16.2: asking for name= of the internal unit in the same inquire makes the program stop with a segmentation fault, so the example asks for form=, which every unit has. Derived-type I/O is covered in Derived-Type and Asynchronous I/O.

Compiler identification

compiler_version() and compiler_options() (S 16.10.2.7 and 16.10.2.6) are transformational functions with no arguments that return a default character scalar of processor-dependent length. The first identifies the translation phase of the processor (name and version) and the second the options that controlled it; the standard says both "should include relevant information that could be useful for diagnosing problems at a later date". They are specification inquiries (S 10.1.11), so they may be used in specification and constant expressions, as in the parameter below, and they are evaluated at translation time, not when the program runs: they describe the compiler that built the program, not the machine it runs on.

program compiler_info
  use, intrinsic :: iso_fortran_env, only: compiler_version, compiler_options
  implicit none
  character(len=*), parameter :: version = compiler_version()
  print '(a)', 'compiler_version(): ' // version
  print '(a,l1)', 'options mention -O2:   ', index(compiler_options(), '-O2') > 0
  print '(a,l1)', 'options mention -std=: ', index(compiler_options(), '-std=') > 0
end program compiler_info

Built with -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all -g, gfortran 16.2 prints:

compiler_version(): GCC version 16.2.0
options mention -O2:   F
options mention -std=: T

and Flang 23:

compiler_version(): Debian flang version 23.1.4
options mention -O2:   F
options mention -std=: F

Rebuilt with -O2, both report -O2 in compiler_options(). The first Flang line shows that its string is simply the driver command line as given (no -std was passed to it). The strings are not meant to be parsed: they differ between compilers, versions and distributions (the Flang string above names the Debian package), and compiler_options() can be long. Use them in log files, bug reports and --version output of command-line tools.

Atomic kinds, image status constants and the synchronization types

The rest of the module supports coarray programs. They are reference material here; the statements that use them are explained on the two coarray pages.

Entity Clause What it is

atomic_int_kind, atomic_logical_kind

16.10.2.2, .3

kinds of integer and logical variables on which the atomic subroutines work

lock_type

16.10.2.19

derived type of lock variables, used with lock and unlock; a named variable of the type shall be a coarray (C1608)

event_type

16.10.2.10

derived type of event variables, used with event post and event wait; coarray only (C1604); count is of kind atomic_int_kind

notify_type

16.10.2.22

F2023: derived type of notify variables, used with the notify= specifier and notify wait; coarray only (C1612)

team_type

16.10.2.34

derived type of team variables, used with form team, change team and team_number

initial_team, current_team, parent_team

16.10.2.12, .8, .25

values of the level argument of get_team

stat_failed_image

16.10.2.28

positive if the processor can detect failed images, otherwise negative; assigned to stat= when an image involved has failed

stat_stopped_image

16.10.2.31

positive; assigned when an image involved has initiated normal termination

stat_locked

16.10.2.29

assigned to stat= of a lock if the executing image already holds the lock

stat_locked_other_image

16.10.2.30

assigned to stat= of an unlock if another image holds the lock

stat_unlocked

16.10.2.32

assigned to stat= of an unlock if the lock is not locked

stat_unlocked_failed_image

16.10.2.33

assigned to stat= of a lock if the lock was released because the image that held it failed

The types have private components, are extensible and have no type parameters. The restrictions that make them safe are constraints of the language (C1603 to C1614): a variable of such a type shall be a coarray, shall not be used in a variable definition context except in the statement that belongs to the type (lock/unlock for locks, event post/event wait for events) or as an allocate-object, and may be passed only to an intent(inout) dummy argument of a procedure with an explicit interface. S 16.10.2.35 requires the values of iostat_inquire_internal_unit, stat_failed_image, stat_locked, stat_locked_other_image, stat_stopped_image, stat_unlocked and stat_unlocked_failed_image to be distinct, so one select case can test the result of any image-control statement.

The processors disagree on the values, which is the reason to compare against the names:

program status_values
  use, intrinsic :: iso_fortran_env, only: stat_failed_image, stat_locked, stat_locked_other_image, &
                                           stat_stopped_image, stat_unlocked, initial_team, current_team, &
                                           parent_team, atomic_int_kind, atomic_logical_kind
  implicit none
  print '(a,i0)', 'stat_failed_image       = ', stat_failed_image
  print '(a,i0)', 'stat_locked             = ', stat_locked
  print '(a,i0)', 'stat_locked_other_image = ', stat_locked_other_image
  print '(a,i0)', 'stat_stopped_image      = ', stat_stopped_image
  print '(a,i0)', 'stat_unlocked           = ', stat_unlocked
  print '(a,i0)', 'initial_team            = ', initial_team
  print '(a,i0)', 'current_team            = ', current_team
  print '(a,i0)', 'parent_team             = ', parent_team
  print '(a,i0)', 'atomic_int_kind         = ', atomic_int_kind
  print '(a,i0)', 'atomic_logical_kind     = ', atomic_logical_kind
end program status_values
Constant gfortran 16.2 Flang 23

stat_failed_image

6001

101

stat_locked

1

102

stat_locked_other_image

2

103

stat_stopped_image

6000

104

stat_unlocked

0

105

initial_team, current_team, parent_team

0, 2, 1

-2, -1, -3

atomic_int_kind, atomic_logical_kind

4, 4

8, 8

Two observations. First, stat_unlocked is 0 in gfortran, the same number that means "success" in any other stat= variable. Second, Flang’s team constants are negative; Flang rejects a program that declares a coarray unless it is built with the experimental -fcoarray option (Multi-image features are experimental and are disabled by default), so the examples that need coarrays were run on gfortran only.

A program that declares the synchronization types needs a coarray build. With gfortran, -fcoarray=single is enough on one image (without any -fcoarray option the compiler stops at the first coarray declaration with Fatal Error: Coarrays disabled at (1), use '-fcoarray=' to enable), while -fcoarray=lib links against libcaf_shmem or OpenCoarrays for several images:

program sync_types
  use, intrinsic :: iso_fortran_env, only: lock_type, event_type, &
                                           stat_locked, stat_locked_other_image, stat_unlocked, &
                                           stat_failed_image, stat_stopped_image, &
                                           atomic_int_kind, atomic_logical_kind
  implicit none
  type(lock_type), save :: lk[*]
  type(event_type), save :: ev[*]
  integer :: st, cnt
  print '(a,*(1x,i0))', 'stat_locked, stat_locked_other_image, stat_unlocked:', &
    stat_locked, stat_locked_other_image, stat_unlocked
  print '(a,*(1x,i0))', 'stat_failed_image, stat_stopped_image:', stat_failed_image, stat_stopped_image
  print '(a,2(1x,i0))', 'atomic_int_kind, atomic_logical_kind:', atomic_int_kind, atomic_logical_kind
  lock (lk[1], stat=st)
  print '(a,i0)', 'lock, stat = ', st
  unlock (lk[1], stat=st)
  print '(a,i0)', 'unlock, stat = ', st
  event post (ev[1])
  event post (ev[1])
  call event_query (ev, cnt)
  print '(a,i0)', 'event count after two posts = ', cnt
  event wait (ev)
  call event_query (ev, cnt)
  print '(a,i0)', 'event count after one wait = ', cnt
end program sync_types

Compiled with gfortran -std=f2023 -Wall -Wextra -pedantic -fimplicit-none -fcheck=all -fcoarray=single:

stat_locked, stat_locked_other_image, stat_unlocked: 1 2 0
stat_failed_image, stat_stopped_image: 6001 6000
atomic_int_kind, atomic_logical_kind: 4 4
lock, stat = 0
unlock, stat = 0
event count after two posts = 2
event count after one wait = 1

This page does not show failure behaviour (a second lock by the same image, an unlock of an unlocked lock): the two gfortran runtimes tried, -fcoarray=single and -fcoarray=lib with libcaf_shmem, returned stat=0 for a repeated lock, and OpenCoarrays returned its own code 99 where the standard names stat_locked, so a portable program cannot yet rely on them. Team statements were also incomplete: a test program that called get_team and form team compiled with -fcoarray=single and then ended silently after its first output line, so the team constants above are shown as values only. The coarray pages treat these statements and these limits in detail.

Which standard introduced what

The provenance below combines the Fortran 2023 text (which lists the F2023 additions in its Introduction) with what gfortran -std= itself reports: each constant was imported alone under -std=f2003, -std=f2008 and -std=f2018, and the first level that accepted it is shown. gfortran 16.2 rejects real16, logical8 to logical64 and notify_type below -std=f2023 (they are F2023 names), and its module does not have stat_unlocked_failed_image or notify_type at all (real16 exists there but has the value -2).

Edition Entities Evidence

Fortran 2003

input_unit, output_unit, error_unit, iostat_end, iostat_eor, character_storage_size, numeric_storage_size, file_storage_size

accepted by -std=f2003; gfortran manual: "Fortran 2003 and later"

Fortran 2008

int8 to int64, real32 to real128, integer_kinds, real_kinds, logical_kinds, character_kinds, atomic_int_kind, atomic_logical_kind, lock_type, stat_locked, stat_locked_other_image, stat_stopped_image, stat_unlocked, iostat_inquire_internal_unit, compiler_version, compiler_options

accepted from -std=f2008

Fortran 2018

event_type, team_type, initial_team, current_team, parent_team, stat_failed_image

accepted from -std=f2018

Fortran 2023

real16, logical8, logical16, logical32, logical64, notify_type

S Introduction: "Intrinsic modules"; gfortran rejects logical32 below -std=f2023

In the 2023 text, edition not confirmed

stat_unlocked_failed_image

S 16.10.2.33; absent from the gfortran 16.2 module, present in Flang 23

The checks behind the table are cheap to repeat. With -std=f95 the intrinsic nature itself is rejected:

17_pre2003.f90:2:20:

    2 |   use, intrinsic :: iso_fortran_env, only: output_unit
      |                    1
Error: Fortran 2003: module nature in USE statement at (1)

and with -std=f2003 the Fortran 2008 names are rejected with a message that names the edition:

Error: Fortran 2008: The symbol 'int64', referenced at (1), is not in the selected standard
Error: Fortran 2008: The symbol 'real128', referenced at (1), is not in the selected standard
Error: Fortran 2008: The symbol 'compiler_version', referenced at (1), is not in the selected standard
Error: Fortran 2023: The symbol 'logical32', referenced at (1), is not in the selected standard

(These are lines of the real output of 16_std_levels.f90, a program that imports int64, real128, compiler_version, atomic_int_kind and logical32.) The same flag therefore doubles as a portability test: building with the oldest -std the project promises to support shows the first entity that the older compilers do not have.

An entity that does not exist

Importing a name that the module does not provide is a compile-time error, on both compilers, even though the module as a whole exists:

program f2023_names
  use, intrinsic :: iso_fortran_env, only: stat_unlocked_failed_image, notify_type
  implicit none
  print *, stat_unlocked_failed_image
end program f2023_names

gfortran 16.2:

14_f2023_names.f90:2:42:

    2 |   use, intrinsic :: iso_fortran_env, only: stat_unlocked_failed_image, notify_type
      |                                          1
Error: Symbol 'stat_unlocked_failed_image' referenced at (1) not found in intrinsic module ISO_FORTRAN_ENV
14_f2023_names.f90:2:70:

    2 |   use, intrinsic :: iso_fortran_env, only: stat_unlocked_failed_image, notify_type
      |                                                                      1
Error: Symbol 'notify_type' referenced at (1) not found in intrinsic module ISO_FORTRAN_ENV
14_f2023_names.f90:4:37:

    4 |   print *, stat_unlocked_failed_image
      |                                     1
Error: Symbol 'stat_unlocked_failed_image' at (1) has no IMPLICIT type

Flang 23 accepts this program and prints 106 (its value of stat_unlocked_failed_image). With only the error appears at the use line with the module name in it; without only, the same missing name appears only where it is used and, in a program without implicit none, as the unrelated "has no IMPLICIT type". Another reason to write both only and implicit none. A compile-time failure is also the correct way to learn that a compiler lacks a feature: a program that must run on both can guard the import in a preprocessed module (as in the vendor-module section below), or not use the feature.

Fortran 2028 additions (draft)

The Fortran 2028 working draft J3/26-007r1 (a draft that will change) adds, in its 17.10.2:

  • procedure_name([context]), source_file([context]) and source_line(), which return the name of the enclosing procedure, the name of the source file and the source line of the reference. procedure_name returns upper-case names such as A:S when context is true; the draft defines the format for module procedures, submodules, internal procedures and bind(c, name=) procedures.

  • max_rank([corank]), the maximum rank of a data object that the processor supports (the draft requires at least 15).

  • completion_type (for the asynchronous collectives of the draft), and the kind constants single_precision, double_precision, standard_integer, standard_logical and system_character.

None of them exists yet in the two compilers used here. The program below imports procedure_name, max_rank, source_file, source_line and double_precision:

program missing
  use, intrinsic :: iso_fortran_env, only: procedure_name, max_rank, source_file, source_line, &
                                           double_precision
  implicit none
  print *, 'hello'
end program missing

gfortran 16.2 reports Error: Symbol 'procedure_name' referenced at (1) not found in intrinsic module ISO_FORTRAN_ENV once per name (five errors), and Flang 23 reports error: 'procedure_name' not found in module 'iso_fortran_env' for each of them as well.

Use these names only after the compiler documentation says it implements them. The page What’s New and Fortran 2028 Preview tracks the draft.

Naming clashes and unqualified use

A use statement without only imports every public entity of the module, which for iso_fortran_env is more than forty names. Declaring a local entity with one of those names in the same scope is an error:

module wide
  use, intrinsic :: iso_fortran_env
  implicit none
contains
  subroutine report()
    integer :: int32
    int32 = 3
    print *, int32
  end subroutine report
end module wide

program name_clash
  use, intrinsic :: iso_fortran_env
  implicit none
  integer :: output_unit
  output_unit = 7
  print *, output_unit
end program name_clash

gfortran 16.2:

15_name_clash.f90:15:14:

   13 |   use, intrinsic :: iso_fortran_env
      |                   2
   14 |   implicit none
   15 |   integer :: output_unit
      |              1~~~~~~~~~~
Error: Symbol 'output_unit' at (1) conflicts with symbol from module 'iso_fortran_env', use-associated at (2)
15_name_clash.f90:16:3:

   16 |   output_unit = 7
      |   1~~~~~~~~~~~
Error: Named constant 'output_unit' in variable definition context (assignment) at (1)

Flang 23: error: 'output_unit' is use-associated from module 'iso_fortran_env' and cannot be re-declared.

The program unit wide in the same file shows the other half of the rule. The subroutine report declares a local integer :: int32 although int32 is use-associated into its host module, and both compilers accept it: a declaration in an inner scope hides the host-associated name, and only a conflict in the same scope is an error. That is legal and hard to read, and it is why an only list (or private in the module plus explicit public statements) is better than relying on the scoping rules. If a processor extends iso_fortran_env with a new public name (S 16.10.1 allows it), a program that imports everything can start failing to compile after a compiler upgrade, and a program that imports with only cannot.

Other standard intrinsic modules at a glance

The three IEEE modules and iso_c_binding have their own pages; this section shows that they are imported in the same way, and what the module-level checks look like.

program other_standard_modules
  use, intrinsic :: iso_fortran_env, only: int32, int64, real32, real64, real128
  use, intrinsic :: iso_c_binding, only: c_int, c_long_long, c_float, c_double, c_long_double, &
                                         c_char, c_null_char, c_ptr, c_null_ptr, c_associated, c_sizeof
  use, intrinsic :: ieee_arithmetic, only: ieee_is_nan, ieee_is_finite, ieee_value, ieee_quiet_nan, &
                                           ieee_positive_inf, ieee_support_datatype, ieee_support_nan
  use, intrinsic :: ieee_exceptions, only: ieee_flag_type, ieee_overflow, ieee_get_flag
  implicit none
  type(c_ptr) :: p
  real(real64) :: x, inf
  logical :: flag
  type(ieee_flag_type) :: f
  integer(c_int) :: ci
  print '(a,4(1x,l1))', 'c kinds equal Fortran kinds (int, long long, float, double):', &
    c_int == int32, c_long_long == int64, c_float == real32, c_double == real64
  print '(a,l1)', 'c_long_double == real128: ', c_long_double == real128
  print '(a,i0,a,l1)', 'c_char kind = ', c_char, ', c_null_char is NUL: ', iachar(c_null_char) == 0
  p = c_null_ptr
  print '(a,l1)', 'c_associated(c_null_ptr): ', c_associated(p)
  print '(a,i0)', 'c_sizeof(ci) = ', c_sizeof(ci)
  x = ieee_value(x, ieee_quiet_nan)
  inf = ieee_value(inf, ieee_positive_inf)
  print '(a,l1,a,l1)', 'is_nan(nan)=', ieee_is_nan(x), ' is_finite(inf)=', ieee_is_finite(inf)
  print '(a,2(1x,l1))', 'ieee_support_datatype, ieee_support_nan for real64:', &
    ieee_support_datatype(x), ieee_support_nan(x)
  f = ieee_overflow
  call ieee_get_flag(f, flag)
  print '(a,l1)', 'overflow flag raised: ', flag
end program other_standard_modules
c kinds equal Fortran kinds (int, long long, float, double): T T T T
c_long_double == real128: T
c_char kind = 1, c_null_char is NUL: T
c_associated(c_null_ptr): F
c_sizeof(ci) = 4
is_nan(nan)=T is_finite(inf)=F
ieee_support_datatype, ieee_support_nan for real64: T T
overflow flag raised: F

Both compilers print the same lines. iso_c_binding supplies the C-compatible kinds (c_int, c_double and so on), the pointer types c_ptr and c_funptr and c_null_char; c_sizeof comes from the same module and is, like compiler_version, a specification inquiry (S 10.1.11). ieee_arithmetic provides the value functions ieee_value, ieee_is_nan and ieee_is_finite, and, because it behaves as if it contained use ieee_exceptions (S 17.1), the flags ieee_overflow and ieee_get_flag are reachable from either module.

Some practical points about these modules:

  • Support is processor dependent for the IEEE modules. A program that needs a particular feature asks for it with the ieee_support_* inquiry functions at run time, or imports the matching constant from ieee_features (for example ieee_halting), because S 17.1 says that the extent of support for halting and the optional exceptions can be influenced by the accessibility of those constants.

  • iso_c_binding kinds are not the iso_fortran_env kinds, though on mainstream platforms they have equal values, as the first line of the output shows. A bind(c) interface must be written with the c_* names, because those are the ones the standard guarantees to correspond to C types (c_int has the kind of C int even on a platform where int32 is a different kind).

  • The entities of these modules are accessible only after use. There is no implicit availability, unlike intrinsic procedures.

Non-standard modules: omp_lib, mpi_f08 and ifport

Fortran code often needs modules that the standard does not define. They look the same in a use statement and behave differently in every other way: they are not available on every compiler, they need a flag or a library, and their names, their entities and sometimes their semantics are the vendor’s.

Module Provided by Needs

omp_lib, omp_lib_kinds

the OpenMP runtime, shipped with the compiler (gfortran, Flang, ifx)

-fopenmp for the directives and the link of the runtime library

mpi_f08 (also mpi, mpi_f08_types)

the MPI implementation (MPICH, Open MPI), not the compiler; defined by the MPI standard

compile with the MPI wrapper mpifort, which adds the include path and libraries

ifport

Intel’s compatibility module for POSIX-style and Windows-style routines (sleepqq, getenv, …​)

an Intel compiler; not available in gfortran or Flang

The module omp_lib shows how a vendor module relates to the nature rule. Both compilers accept use, intrinsic :: omp_lib (both treat it as an intrinsic module that they ship) and reject use, non_intrinsic :: omp_lib:

program nature
  use, intrinsic :: omp_lib, only: omp_get_max_threads
  implicit none
  print '(a,i0)', 'max threads: ', omp_get_max_threads()
end program nature

Compiled with -fopenmp and run with OMP_NUM_THREADS=2, gfortran 16.2 and Flang 23 print max threads: 2. With non_intrinsic:

22_nonintrinsic_nature.f90:2:24:

    2 |   use, non_intrinsic :: omp_lib, only: omp_get_max_threads
      |                        1
Fatal Error: Cannot open module file 'omp_lib.mod' for reading at (1): No such file or directory
compilation terminated.

That is the compiler’s choice, not the standard’s: S 16.10 lists five standard intrinsic modules, so omp_lib being intrinsic is a vendor extension (S 16.10.1 allows a processor to provide more modules and entities). A bare use omp_lib works on both compilers.

What goes wrong without the flag, and with a module that is not there

omp_lib compiles without -fopenmp but does not link, because the runtime library is only linked with the flag:

program omp_probe
  use omp_lib, only: omp_get_max_threads, omp_get_wtime
  implicit none
  print '(a,i0)', 'max threads: ', omp_get_max_threads()
  print '(a,l1)', 'wtime is positive: ', omp_get_wtime() > 0.0d0
end program omp_probe
/usr/bin/ld: /tmp/cclbzZXT.o: in function `MAIN__':
18_omp_lib.f90:(.text+0x64): undefined reference to `omp_get_max_threads_'
/usr/bin/ld: 18_omp_lib.f90:(.text+0xdc): undefined reference to `omp_get_wtime_'
collect2: error: ld returned 1 exit status

That is the gfortran 16.2 link error without -fopenmp (the object file name varies from run to run); Flang 23 gives the same kind of error (undefined reference to 'omp_get_max_threads'). With -fopenmp the program prints max threads: 3 for OMP_NUM_THREADS=3. Without the variable the runtime picks the thread count itself (the number of cores, in our container).

A module that is simply absent gives a different failure, at compile time. This is ifport on gfortran 16.2 (the program is 26_ifport.f90, a five-line program that imports sleepqq):

26_ifport.f90:2:7:

    2 |   use ifport, only: sleepqq
      |       1
Fatal Error: Cannot open module file 'ifport.mod' for reading at (1): No such file or directory
compilation terminated.

and Flang 23: error: Cannot parse module file for module 'ifport': Source file 'ifport.mod' was not found. ifport was not run with Intel ifx, which was not available, and the text above does not claim anything about what it provides beyond its role.

For mpi_f08 the compile line is the whole story. The module is provided by the MPI installation, and the MPI Forum defines its contents (the mpi_f08 Fortran binding is part of the MPI standard):

program mpi_probe
  use mpi_f08, only: mpi_init, mpi_finalize, mpi_comm_world, mpi_comm_rank, mpi_comm_size
  implicit none
  integer :: rank, nranks
  call mpi_init()
  call mpi_comm_rank(mpi_comm_world, rank)
  call mpi_comm_size(mpi_comm_world, nranks)
  if (rank == 0) print '(a,i0)', 'ranks: ', nranks
  call mpi_finalize()
end program mpi_probe

Built with mpifort -std=f2023 (the wrapper uses gfortran 16.2 here) and run with mpirun -n 3:

ranks: 3

It also builds warning-free with -std=f2018 -Wall -Wextra -pedantic. Notice that the strict -std flag does not reject the module: standard conformance checks apply to the source, not to the vendor module it imports. The program does not need mpi_f08 to be intrinsic, and it is not; the compiler finds mpi_f08.mod through the wrapper’s -I flags. Parallel programming is covered in OpenMP and OpenACC and MPI with mpi_f08.

Keeping vendor modules isolated

Because these modules are not portable, the practical rule is that only one module in the project may import each of them, and everything else imports that module. The rest of the program then compiles on a machine without OpenMP, MPI or the vendor library, and a port to another compiler changes one file. In heatflow the idea looks like this:

flowchart TB S["heatflow_solver, heatflow_kernels"] --> T["heatflow_threads"] A["app/main.f90"] --> T A --> P["heatflow_parallel"] T --> O["omp_lib (OpenMP runtime)"] P --> M["mpi_f08 (MPI library)"] S --> K["heatflow_kinds"] K --> E["iso_fortran_env"] T --> E

heatflow_threads is the only module that knows about omp_lib. It compiles in two configurations: with OpenMP, where it forwards to the runtime, and without, where it provides serial stand-ins with the same interface. The choice is made by the preprocessor macro _OPENMP that compilers define under -fopenmp. The file needs a preprocessed extension (.F90):

! heatflow_threads.F90 -- the only place that knows about omp_lib
module heatflow_threads
#ifdef _OPENMP
  use omp_lib, only: omp_get_max_threads, omp_get_wtime
#endif
  use, intrinsic :: iso_fortran_env, only: int64, real64
  implicit none
  private
  public :: thread_count, wall_time, threading_name
contains
  integer function thread_count()
#ifdef _OPENMP
    thread_count = omp_get_max_threads()
#else
    thread_count = 1
#endif
  end function thread_count

  real(real64) function wall_time()
#ifdef _OPENMP
    wall_time = omp_get_wtime()
#else
    integer(int64) :: count, rate
    call system_clock(count, rate)
    wall_time = real(count, real64) / real(rate, real64)
#endif
  end function wall_time

  function threading_name() result(name)
    character(len=:), allocatable :: name
#ifdef _OPENMP
    name = 'OpenMP'
#else
    name = 'serial'
#endif
  end function threading_name
end module heatflow_threads

program use_threads
  use heatflow_threads, only: thread_count, wall_time, threading_name
  implicit none
  real :: t0
  t0 = real(wall_time())
  print '(a,a,a,i0)', 'build: ', threading_name(), ', threads: ', thread_count()
  print '(a,l1)', 'clock is monotonic: ', real(wall_time()) >= t0
end program use_threads

The program runs unchanged in four configurations:

Build Output

gfortran 16.2 -std=f2023 and the other strict flags

build: serial, threads: 1, clock is monotonic: T

gfortran 16.2 -fopenmp, OMP_NUM_THREADS=4

build: OpenMP, threads: 4, clock is monotonic: T

Flang 23 (no flag)

build: serial, threads: 1, clock is monotonic: T

Flang 23 -fopenmp, OMP_NUM_THREADS=4

build: OpenMP, threads: 4, clock is monotonic: T

The same pattern isolates mpi_f08 in a heatflow_parallel module (not shown) and any vendor library: public procedures with standard types in the signature, vendor types private to the module, and a serial fallback or a clear error when the capability is absent. Points that make the pattern work:

  • Export a minimal interface of standard types. The wrapper above returns a default integer and a real64, not an OpenMP type.

  • Use private and an explicit public list, so that the vendor module’s names are not re-exported.

  • Choose the module by build system, not by comments. fpm, CMake and Meson can add -fopenmp or the MPI wrapper per target; see Building with Make, CMake and Meson and Fortran Package Manager (fpm).

  • Guard with the preprocessor only inside the facade. Scattering #ifdef through the numerical code makes it unreadable.

  • Say which compilers have been tested. Vendor modules are where "works on my compiler" begins.

A heatflow environment module

A project module that imports iso_fortran_env once, with only, and exports small, purpose-built procedures is the recommended way to use the module: callers see log_info and log_error, not error_unit, and the dependency on one particular stream is in one place. The example below contains the kinds module of the running project (a demo-named copy so it does not clash with the project one), a logging and environment module, and a program that exercises both, including a scratch file opened with newunit=:

module heatflow_kinds_demo
  use, intrinsic :: iso_fortran_env, only: int32, int64, real32, real64
  implicit none
  private
  public :: sp, dp, ip, lp
  integer, parameter :: sp = real32, dp = real64, ip = int32, lp = int64
end module heatflow_kinds_demo

module heatflow_env
  use, intrinsic :: iso_fortran_env, only: output_unit, error_unit, iostat_end, compiler_version
  use heatflow_kinds_demo, only: dp
  implicit none
  private
  public :: log_info, log_error, build_banner, read_temperatures
contains
  subroutine log_info(msg)
    character(len=*), intent(in) :: msg
    write (output_unit, '(a)') 'info: ' // msg
  end subroutine log_info

  subroutine log_error(msg)
    character(len=*), intent(in) :: msg
    write (error_unit, '(a)') 'error: ' // msg
  end subroutine log_error

  function build_banner() result(text)
    character(len=:), allocatable :: text
    text = 'heatflow built with ' // compiler_version()
  end function build_banner

  ! Read temperatures from an already opened unit until end of file.
  subroutine read_temperatures(unit, values, stat)
    integer, intent(in) :: unit
    real(dp), allocatable, intent(out) :: values(:)
    integer, intent(out) :: stat
    real(dp) :: t
    integer :: ios
    allocate (values(0))
    stat = 0
    do
      read (unit, *, iostat=ios) t
      if (ios == iostat_end) exit
      if (ios /= 0) then
        stat = ios
        return
      end if
      values = [values, t]
    end do
  end subroutine read_temperatures
end module heatflow_env

program use_env
  use heatflow_kinds_demo, only: dp
  use heatflow_env, only: log_info, log_error, build_banner, read_temperatures
  implicit none
  real(dp), allocatable :: v(:)
  integer :: stat, u
  character(len=40) :: line
  call log_info(build_banner())
  open (newunit=u, status='scratch', action='readwrite')
  write (u, '(a)') '20.5', '21.0', '19.75'
  rewind (u)
  call read_temperatures(u, v, stat)
  write (line, '(a,i0,a,f6.2)') 'read ', size(v), ' values, mean ', sum(v) / real(size(v), dp)
  call log_info(trim(line))
  close (u)
  open (newunit=u, status='scratch', action='readwrite')
  write (u, '(a)') '20.5', 'oops'
  rewind (u)
  call read_temperatures(u, v, stat)
  if (stat /= 0) then
    write (line, '(a,i0)') 'bad record, iostat=', stat
    call log_error(trim(line))
  end if
  close (u)
end program use_env

Standard output (identical on gfortran 16.2 and Flang 23 apart from the compiler string):

info: heatflow built with GCC version 16.2.0
info: read 3 values, mean  20.42

Flang 23 prints info: heatflow built with Debian flang version 23.1.4 as the first line. Standard error:

error: bad record, iostat=5010

Flang 23 prints iostat=1030 for the same bad record. Everything else is the same on both. Points worth noting:

  • The read loop tests iostat == iostat_end first, as the exit condition, and treats any other nonzero value as an error to report. The actual numbers (5010, 1030) are processor dependent and meant for the log, not for comparisons.

  • compiler_version() is called inside a function, giving a banner that records which compiler built the program.

  • values = [values, t] grows the array by reallocation on assignment (see Allocatable Arrays and Dynamic Memory); it is shown for brevity and is quadratic, so a real reader would count lines first or double the capacity.

  • The two modules use private by default and a public list; only the procedures leave the module, and the kinds module is the same one that every other heatflow module imports from.

Pitfalls

  • Literal kind numbers. real(8), integer(4) and selected_real_kind results copied into source are the oldest portability bug in Fortran; use iso_fortran_env or a kinds module.

  • Negative kind constants. real16 is -2 on gfortran 16.2. A declaration with it fails to compile, a merge fallback does not (see 04_defensive_kinds.f90).

  • Assuming a kind number equals bytes. True on gfortran and Flang, not guaranteed by the standard; do not compute with kind values, compare them.

  • Bare use iso_fortran_env. It imports everything, can be shadowed by a user module of the same name, and breaks when a compiler adds names. Write the nature, the module and an only list.

  • Literal units 5, 6 and 0. Use the named constants, and newunit= for files.

  • Comparing iostat to -1. Use iostat_end and iostat_eor.

  • Comparing stat to a literal. stat_unlocked is 0 on one compiler and 105 on another; compare to the name.

  • Declaring a lock, event, team or notify variable that is not a coarray is a constraint violation (C1604, C1608, C1612); declaring a coarray without a coarray build flag is a compile error on gfortran and on Flang.

  • Using stat_unlocked_failed_image or notify_type on gfortran 16.2: the module does not have them.

  • Vendor module names in the middle of numerical code. One facade per vendor module.

  • use, non_intrinsic for a compiler-supplied module such as omp_lib fails, because there is no module file.

  • Reading compiler_options() as if it were a flag list. It is a diagnostic string and its format is the compiler’s.

  • Treating Fortran 2028 names as available. procedure_name, source_file, source_line and max_rank are not in either compiler yet.

Where older material is outdated

Older Fortran material, such as Fortran 77 code and early Fortran 90 tutorials, does not know these modules, and some material that predates 2008 or 2023 is silent on their later additions. The main changes:

  • Kinds. Old code writes real*8, double precision and integer*4; modern code writes real64 and int32 from iso_fortran_env (Fortran 2008). real16, logical8 to logical64 and notify_type arrived in Fortran 2023.

  • Units. Old code writes read (5, ...) and write (6, ...), and uses unit 0 for errors on some systems; the named constants are the standard way since Fortran 2003.

  • Environment queries. Old code calls compiler-specific routines for the compiler name. compiler_version and compiler_options are standard since Fortran 2008.

  • Vendor portability modules. Old programs import ifport-style modules for getenv, system and sleep; modern Fortran has get_environment_variable, execute_command_line and command_argument_count, which need no module (see Intrinsic Procedures Overview).

  • Intrinsic module nature. use, intrinsic exists only since Fortran 2003, which is why code that must still build with a Fortran 95 compiler cannot use it.

  • IEEE modules. Material written against IEEE 754-2008 and ISO/IEC/IEEE 60559:2011 is older than the 2019/2020 revision that Fortran 2023 adopts.