Do IEEE modes reset automatically upon return?

This is a continuation of an interesting tangent in the Poll thread.

Here is a short program that tests whether the compiler (processor) automatically resets the underflow mode upon return from a subprogram.

program ieee
   use, intrinsic :: iso_fortran_env, wp=>real32
   use, intrinsic :: ieee_arithmetic
   implicit none
   real(wp) :: x
   if ( .not. ieee_support_underflow_control(x) ) then
      write(*,*) 'underflow control is not supported for kind(x)=', kind(x)
      stop
   endif
   write(*,*) 'underflow control is supported for kind(x)=', kind(x)
   x = tiny(x) * 1.e-5_wp
   call print_stuff('default',x)
   call toggle_mode()
   x = tiny(x) * 1.e-5_wp
   call print_stuff('first toggle',x)
   call toggle_mode()
   x = tiny(x) * 1.e-5_wp
   call print_stuff('second toggle',x)
contains
   subroutine print_stuff(title,x)
      ! print ieee info about x.
      character(*), intent(in) :: title
      real(wp), intent(in) :: x
      write(*,*) '==== ', title, ' ===='
      write(*,*) 'x=', x
      write(*,*) 'ieee_support_datatype(x)=', ieee_support_datatype(x)
      write(*,*) 'ieee_support_subnormal(x)=', ieee_support_subnormal(x)
      write(*,*) 'subnormal test =', (ieee_class(x) == ieee_positive_subnormal)
      return
   end subroutine print_stuff
   subroutine toggle_mode()
      ! toggle the underflow mode.
      logical :: gradual
      call ieee_get_underflow_mode( gradual )
      write(*,*) 'toggle: input underflow mode is ', gradual
      call ieee_set_underflow_mode( gradual = (.not. gradual) )
      call ieee_get_underflow_mode( gradual )
      write(*,*) 'toggle: new underflow mode is ', gradual
   end subroutine toggle_mode
end program ieee

$ gfortran ieee.f90 && a.out
 underflow control is supported for kind(x)=           4
 ==== default ====
 x=   1.17709071E-43
 ieee_support_datatype(x)= T
 ieee_support_subnormal(x)= T
 subnormal test = T
 toggle: input underflow mode is  T
 toggle: new underflow mode is  F
 ==== first toggle ====
 x=   0.00000000    
 ieee_support_datatype(x)= T
 ieee_support_subnormal(x)= T
 subnormal test = F
 toggle: input underflow mode is  F
 toggle: new underflow mode is  T
 ==== second toggle ====
 x=   1.17709071E-43
 ieee_support_datatype(x)= T
 ieee_support_subnormal(x)= T
 subnormal test = T

$ flang ieee.f90 && a.out
 underflow control is supported for kind(x)= 4
 ==== default ====
 x= 1.18E-43
 ieee_support_datatype(x)= T
 ieee_support_subnormal(x)= T
 subnormal test = T
 toggle: input underflow mode is  F
 toggle: new underflow mode is  F
 ==== first toggle ====
 x= 1.18E-43
 ieee_support_datatype(x)= T
 ieee_support_subnormal(x)= T
 subnormal test = T
 toggle: input underflow mode is  F
 toggle: new underflow mode is  F
 ==== second toggle ====
 x= 1.18E-43
 ieee_support_datatype(x)= T
 ieee_support_subnormal(x)= T
 subnormal test = T

$ nagfor ieee.f90 && a.out
NAG Fortran Compiler Release 7.2(Shin-Urayasu) Build 7203
[NAG Fortran Compiler normal termination]
 underflow control is supported for kind(x)= 1
 ==== default ====
 x=   1.1770907E-43
 ieee_support_datatype(x)= T
 ieee_support_subnormal(x)= T
 subnormal test = T
 toggle: input underflow mode is  T
 toggle: new underflow mode is  F
 ==== first toggle ====
 x=   1.1770907E-43
 ieee_support_datatype(x)= T
 ieee_support_subnormal(x)= T
 subnormal test = T
 toggle: input underflow mode is  T
 toggle: new underflow mode is  F
 ==== second toggle ====
 x=   1.1770907E-43
 ieee_support_datatype(x)= T
 ieee_support_subnormal(x)= T
 subnormal test = T

This is on MacOS with arm64 hardware. Gfortran behaves as I had expected. Namely, the programmer can reset the underflow mode in a subroutine, and that reset sticks after return for the rest of the program. Is this standard conforming behavior?

Flang for some reason does not allow the underflow mode to be changed, despite saying that it can be changed with the ieee_support_underflow_control(x) function. The result of ieee_get_underflow_mode() seems to be incorrect. Otherwise, the other ieee tests on the value seem to work correctly. Maybe I’m doing something wrong in the toggle_mode() subroutine? BTW, flang also prints some run time warning messages about the underflows to stderr, which I did not include in the above output.

The NAG fortran compiler does seem to reset the underflow mode upon return from toggle_mode(). If you move the ieee_set_underflow_mode() call to the main program instead of the subroutine, then the mode toggles as expected. This is the opposite of my expectations. On the downside however, when you do that the subnormal test prints T when the argument is 0.0, so I don’t think that part is right. However, I’m still unsure if I’m doing that test correctly.

FWIW, I tried your program with ifx version 2025.0.0 on Windows and got essentially the same output as you show for the NAG compiler.

The Standard uses identical or very similar words for

  • the IEEE exceptions : If a flag is signaling on entry to a procedure other than IEEE_GET_FLAG or IEEE_GET_STATUS, the processor will set it to quiet on entry and restore it to signaling on return. If a flag signals during execution of a procedure, the processor shall not set it to quiet on return.
  • the IEEE rounding modes : In a procedure other than IEEE_SET_ROUNDING_MODE or IEEE_SET_STATUS, the processor shall not change the rounding modes on entry, and on return shall ensure that the rounding modes are the same as they were on entry.
  • the IEEE underflow mode :In a procedure other than IEEE_SET_UNDERFLOW_MODE or IEEE_SET_STATUS, the processor shall not change the underflow mode on entry, and on return shall ensure that the underflow mode is the same as it was on entry.
  • the IEEE halting mode : In a procedure other than IEEE_SET_HALTING_MODE or IEEE_SET_STATUS, the processor shall not change the halting mode on entry, and on return shall ensure that the halting mode is the same as it was on entry.

I must admit that when I read that text (all of the ones you quote, not just for gradual underflow), I cannot parse what it says in a unique way. Let’s focus on the underflow case since that is what I used in the above code.

The first prepositional phrase “In a procedure other than IEEE_SET_UNDERFLOW_MODE or IEEE_SET_STATUS” says that those two procedures are exempted from the restriction that follows. The restriction that follows is that the processor is not allowed to change the underflow mode, either temporarily or permanently, within any other procedure.

When I read that, I’m thinking it means that the programmer has control of the underflow mode, and that the processor (e.g. within the matmul() intrinsic procedure, or the dot_product() intrinsic procedure, or any other procedure provided by the processor) is not allowed to reset the mode, perform the operation, and then reset it back, or to reset the mode, perform the operation, and leave the mode changed from what it was on entry. I read that as a restriction on the processor, not on the programmer. I read it as the programmer is still free, through those two set routines, to change the underflow mode however he wants, either temporarily or permanently. If the programmer wants only a temporary change, then he must query the mode and save the entry value, change the mode, perform the operations within that procedure, and then restore the original mode upon return. If he wants a permanent change, then he makes it through those two special procedures, and that mode remains that way until it is changed at some later time by the programmer.

The other way to parse that sentence is that the programmer is not allowed to change the underflow mode within a subroutine, and then have that change persist past the return statement. The processor must insert whatever necessary code is required upon return to restore the underflow mode back to its original state upon entry. This interpretation of the text is similar to the state of a local allocatable entity, which is always restored to a deallocated state upon return, whether the programmer does it explicitly or the processor does it automatically.

So I guess the question in this thread is which interpretation is intended by the standard? It looks so far like gfortran is consistent with the first interpretation, and that nagfor and ifx are consistent with the second interpretation. The flang compiler does not allow the mode to be reset, so it is broken in this respect and cannot be used to support either interpretation. Has there been an official interpretation by the standard committee on this question?

@sblionel and @tmj, I believe are attending J3 tomorrow so perhaps they would be kind enough to let us know.

I view the standard’s text, as excerpted by @themos , to be clear. Changes to the various IEEE modes within a procedure are reverted on return from that procedure. The “first prepositional phrase” is simply intended to say that those two intrinsic module procedures can change the mode(s) and leave them changed upon return - otherwise there would be no point to them.

As for MATMUL, DOT_PRODUCT, etc., it seems clear to me that they are allowed to change IEEE modes as long as they revert them upon return.

Just out of curiosity, I Iooked back through some old reference books to see if this situation was explained there. The IEEE floating point support was first added in f95, so I looked first in “Fortran 90/95 Explained” by Metcalf and Reid. It mentions ieee_set_rounding_mode(), but not ieee_set_underflow_mode(); in the description of that subroutine, there is no mention that the mode must be reset automatically by the compiler (processor) upon return. The only example that is given shows how a programmer can store the original value, reset to a new mode, perform calculations with the new mode, and then restore the mode to the original value. This is on page 264 if anyone else is interested in reading this text.

Then skipping forward to “fortran 95/2003 explained”, it mentions both ieee_set_rounding_mode() and ieee_set_underflow_mode(). The feature of the compiler resetting either of these modes automatically upon return is not mentioned. The same example of getting the original value of the rounding mode and restoring is given. This is on page 232.

Then skipping forward to “Modern Fortran Explained” (the green version which covers f2008), the section covering those two routines appears to be identical to the earlier f2003 version. There is still no mention that a compiler must reset the mode automatically upon return from a procedure. This is on page 236 of that book.

Then skipping forward to “Modern Fortran Explained” (the red version, that covers f2018), the text is unchanged, and still no mention of the compiler resetting these modes automatically upon return. This is page 360 of this edition.

In the latest version of “Modern Fortran Explained” (the rust colored one that covers f2023), the text describing these two routines is modified a little because of the description of the optional radix argument, but it still does not mention the feature that the mode must be restored automatically upon return by the compiler. This text is on page 387.

I also see on pages 676-677 of “The Fortran 2003 Handbook” by Adams, Brainerd, Hendrikson, Maine, Martin, and Smith the descriptions of ieee_set_rounding_mode() and ieee_set_underflow_mode(). There is no mention in either place that a compiler must restore the original values upon return from a procedure. However, the IEEE discussions appear in several places in this book, so it is possible it is mentioned elsewhere.

In the standard fortran documents, my f95 draft document does not have any of the IEEE support. I presume that was added late in the process or something, and my draft version is not the last one? The f2003 draft document states

“In a procedure other than IEEE_SET_ROUNDING_MODE or IEEE_SET_STATUS, the processor shall not change the rounding mode on entry, and on return shall ensure that the rounding mode is the same as it was on entry.”

and

“In a procedure other than IEEE_SET_UNDERFLOW_MODE or IEEE_SET_STATUS, the processor shall not change the underflow mode on entry, and on return shall ensure that the underflow mode is the same as it was on entry.”

I think that text remains mostly unchanged in the standards documents since f2003 (and maybe since f95). So this concept of the compiler automatically resetting these modes upon return appears to be either overlooked or read (by me and others) according to the first interpretation above (ala gfortran) all that time.

@themos @sblionel This is extremely interesting - I apologize my knowledge of the IEEE module in Fortran is limited. Is there a standard-compliant way one could suppress the stderr printing of the IEEE errors triggered by the program, and read said states into the code at runtime instead?

The IEEE stuff was first described in a TR between F95 and F2003. The IEEE TR was issued about the same time as the “Allocatable TR” - which described additional capabilities for allocatable variables. (E.g., allocatables in derived types, as dummy arguments, and function returns.) Both TRs were incorporated into F2003.

Are you referring to the output sent to ERROR_UNIT at the end of a program, that describes the state of the IEEE exceptions? If so, the standard-conforming way (since F2018) is to use STOP [code],QUIET=.true. or ERROR STOP [code],QUIET=.true.

The state of the exception flags can be queried at all times with IEEE_GET_FLAG.
Example: After
CALL IEEE_GET_FLAG(IEEE_OVERFLOW,foo_logical)
the logical variable foo_logical will be .true. if the overflow exception flag is set (signaling) and .false. otherwise.

What does 18.6 “The rounding mode” say?

The reason that this behaviour was chosen (I surmise) is to allow the optimizer to do its job. Constant folding/propagation is a very common optimization, it lets some computation happen at compile time. If the compiler could not decide what the underflow mode or the rounding mode was at the time the statement containing an expression to be optimized was encountered, the opportunities for compile-time evaluation would be severely limited. As it is, the compiler only has to check if a handful of IEEE_* subroutines are directly called from the subprogram under optimization. If none, then the compiler knows what the mode will be at runtime and can compute with that mode at compile time. If the semantics were the alternative (or IEEE_SET_* is called), then it cannot safely perform compile-time computation and should not do so.

Changes made by the processor should be reverted on return from a procedure. We should not blindly clobber a user’s explicit setting of the x87 FPU Control Register. Fortran provides standard means (the IEEE_GET_* calls) for the user to save and restore the FPU Control Register in their procedures should they want to avoid propagation of their local changes to the entire thread of operation.

This would be true in a main program, but not in a subprogram. Whatever the programmer does previously in the calling sequence is still unknown to the compiler when it compiles a particular subprogram. And even with the second interpretation (ala nagfor), those changes by the programmer at one level within the code do apply thereafter to the lower levels in the calling sequence.

In my program above, I wrote the statements x=tiny(x)*1.e-5_wp to see if this actually happens. That is, a compiler could reset the underflow mode, but if that statement were evaluated at compile time, then it would always be the same regardless of the mode at the time that it was intended to be executed. So far, no compiler seems to be doing that, but it might be appropriate to change the code to explicitly test for that possibility too since the statement is in a main program and a compiler might recognize that as a special case.

Yes, thank you for raising that.

The point remains that the analyser/optimizer only has to deal with a handful of cases for what the hardware mode is. In the usual case, where it is detected that no IEEE_SET_… subroutines are called directly from the subprogram, one only has to analyse constant propagation under that handful of cases. One does not need to worry about hardware mode changing half-way through, or at points that can only be determined at runtime. There is no combinatorial explosion. In the unusual case, where IEEE_SET_… is detected, you might still be able to find large enough blocks where things are steady (loop bodies, one hopes).

Yes, this is some relevant text. Also, 18.7 which has similar language for the underflow mode. Here is the 18.7 text on page 353:

“Some processors support alteration of the underflow mode during execution, that is whether small values are represented as denormalized values or ar set to zero. The reason is likely to be that such a processor executes much faster without denormalized values. the underflow mode is said to be gradual if denormalized values are employed. If the underflow mode may be altered at run time, the subroutine ieee_set_underflow_mode (Section 18.9.4) may be used to alter it. The function ieee_support_underflow_control (Section 18.9.2) may be used to inquire whether this facility is available for a particular kind of real.”

“In a procedure other than ieee_set_underflow_mode the processor does not change the underflow mode on entry, and on return ensures that it is the same as it was on entry”

That last sentence is pretty much the same as that in the standard itself. Now that I know where to look, I went back to the green version (f2008), and that sentence is also in section 11.7 on page 229, and in “Fortran 95/2003 Explained” it is in section 11.7 on page 225, and in “Fortran 90/95 Explained” it is in section 12.7 on page 257.

However, since this sentence uses the same language as the standard, it provides no additional information about how that sentence should be interpreted (ala gfortran, or ala nagfor).

Yes, I see now what they did. I thought since it was in the book, it must have been in the standard, but the text at the beginning of that chapter explains the situation. They are explaining the TR, which was then incorporated into the next standard f2003.

Here is an updated version of the test program that includes the toggle of the underflow mode in the main program along with in a subroutine.

program ieee
   use, intrinsic :: iso_fortran_env, wp=>real32
   use, intrinsic :: ieee_arithmetic
   use, intrinsic :: ieee_features, only: ieee_subnormal
   implicit none
   real(wp) :: x
   logical :: gradual_mode, gradual_tmp
   character(*), parameter :: cfmt='(*(g0.4:1x))'
   print cfmt, 'performing tests with kind(x)=', kind(x)
   print cfmt, 'ieee_support_datatype(x)=', ieee_support_datatype(x)
   print cfmt, 'ieee_support_subnormal(x)=', ieee_support_subnormal(x)
   if ( .not. ieee_support_underflow_control(x) ) then
      print cfmt, 'underflow control is not supported for kind(x)=', kind(x)
      stop
   endif
   print cfmt, 'underflow control is supported for kind(x)=', kind(x)

   call ieee_get_underflow_mode( gradual_mode )
   gradual_tmp = gradual_mode
   print cfmt, 'default gradual_mode=', gradual_mode
   call print_stuff('default',x)
   call ieee_set_underflow_mode( gradual = (.not. gradual_mode) )
   call ieee_get_underflow_mode( gradual_mode )
   print cfmt, 'after toggle in main, gradual_mode=', gradual_mode, 'toggle success=', gradual_tmp .neqv. gradual_mode
   call print_stuff('from main',x)

   gradual_tmp = gradual_mode
   call toggle_mode()  ! try to toggle the mode in a subroutine.
   call ieee_get_underflow_mode( gradual_mode )
   print cfmt, 'in main, gradual_mode=', gradual_mode,  'toggle success=', gradual_tmp .neqv. gradual_mode
   call print_stuff('after first subroutine toggle',x)

   gradual_tmp = gradual_mode
   call toggle_mode()  ! try to toggle the mode in a subroutine.
   call ieee_get_underflow_mode( gradual_mode )
   print cfmt, 'in main, gradual_mode=', gradual_mode, 'toggle success=', gradual_tmp .neqv. gradual_mode
   call print_stuff('after second subroutine toggle',x)
contains
   subroutine print_stuff(title,x)
      ! print ieee info about x.
      character(*), intent(in) :: title
      real(wp), intent(inout) :: x
      ! this statement either results in a subnormal or flushes to 0.0,
      ! depending on the current underflow mode.
      x = tiny(x) * 1.e-5_wp
      print cfmt, '====', title, '===='
      print cfmt, 'x=', x
      print cfmt, 'subnormal test=', (ieee_class(x) == ieee_positive_subnormal)
      return
   end subroutine print_stuff
   subroutine toggle_mode()
      ! toggle the underflow mode.
      logical :: gradual_initial, gradual_final
      call ieee_get_underflow_mode( gradual_initial )
      call ieee_set_underflow_mode( gradual = (.not. gradual_initial) )
      call ieee_get_underflow_mode( gradual_final )
      print cfmt, 'toggle: gradual_initial=', gradual_initial, 'gradual_final=', gradual_final, &
           & 'success=', gradual_initial .neqv. gradual_final
   end subroutine toggle_mode
end program ieee