j3-fortran / fortran_proposals

Proposals for the Fortran Standard Committee
175 stars 14 forks source link

Use Cases for Exception Handling in Fortran #66

Open FortranFan opened 4 years ago

FortranFan commented 4 years ago

I request this thread be used to collect examples, thoughts, and comments on the use cases for Exception Handling in Fortran.

As reference, please see this paper from the J3 meeting in Las Vegas earlier this year which documents a set of closely related cases.

Exception Handling is a facility long requested by practitioners but which keeps getting deferred. It was under consideration for Fortran 202X but WG5 resolved at the Tokyo meeting this past August not to include it in Fortran 202X. It is now unclear when, if ever, Exception Handling will be standardized in Fortran.

However the concept remains important and perhaps the FOSS and user community can take the lead in distilling further the needs, in ironing out the wrinkles, also "smoothing" out the edge cases that can possibly cause "bleeding" either in terms of performance or added vulnerabilities in programs, and may be even prototype ideas in implementations such as LFortran, etc.

FortranFan commented 4 years ago

In issue #64 , @klausler wrote:

Note that RETURN with alternate returns would be much more useful if it were possible to forward an incoming alternate return dummy argument as an outgoing alternate return label. Many of the use cases for exception handling would be covered with safe deallocation and finalization if such forwarding were supported, and such support would be trivial to implement.

Note @arjenmarkus has a nice paper on the applicability of "ALTERNATE RETURN" (now obsolescent) facility toward Exception handling in Fortran.

I agree with @arjenmarkus' and @klauer's ideas that a certain (limited?) form of Exception Handling, which might meet a bulk of the use cases for Fortranners especially in the domains of scientific and technical computing, may be feasible by availing the existing compiler implementations on ALTERNATE RETURNs but which may be simply repackaged with some additional facilities like forwarding and improved syntax that addresses concerns not only with performance but also convenience and ease-of-use in modern codes in Fortran.

certik commented 4 years ago

See also #6 for error handling.

FortranFan commented 4 years ago

See also #6 for error handling.

Good point, I missed out on that thread.

gronki commented 4 years ago

Could anyone please give an example how to use revived alternate returns for exception handling? I think the desired behavior is "handle the exception yourself" or "don't handle exception and crash the program". Can you show a simple procedure and two calls (once where error is handled and once where it is unhandled)? How to guarantee that the execution of the program will stop if the error is unhandled? The behavior to quietly proceed with execution in absence of error handler is very dangerous.

sob., 2 lis 2019 o 15:30 FortranFan notifications@github.com napisał(a):

See also #6 https://github.com/j3-fortran/fortran_proposals/issues/6 for error handling.

Good point, I missed out on that thread.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/j3-fortran/fortran_proposals/issues/66?email_source=notifications&email_token=AC4NA3OBGJYWUGNO2TGYQ43QRWFHNA5CNFSM4JIFBE5KYY3PNVWWK3TUL52HS4DFVREXG43VMVBW63LNMVXHJKTDN5WW2ZLOORPWSZGOEC45D3A#issuecomment-549048812, or unsubscribe https://github.com/notifications/unsubscribe-auth/AC4NA3POINI3ONM5WWLF6FLQRWFHNANCNFSM4JIFBE5A .

FortranFan commented 4 years ago

@gronki wrote:

Could anyone please give an example how to use revived alternate returns for exception handling? I think the desired behavior is "handle the exception yourself" or "don't handle exception and crash the program". Can you show a simple procedure and two calls (once where error is handled and once where it is unhandled)? How to guarantee that the execution of the program will stop if the error is unhandled? ..

There are no guarantees with anything but nominally the following might be a rough "template" on how the concept of ALTERNATE RETURNs can be employed by implementations to mimic Exception Handling like the TRY-CATCH constructs.

Say there is a library subprogram with a 'contracted' interface that operates on a dummy argument of real type 'x' that is discovered to encounter an exception for values in a certain range of x. The library code can refactor the method to issue an ERROR STOP and hope the caller can deal with the consequences:

   subroutine sub_x( x )
      real, intent(inout) :: x
      ! An arbitrary range that causes an exception
      if ( (x > 0.4).and.(x < 0.6) ) then
         error stop "In sub_x: invalid value of x"
      end if
      x = x + 1.0
      return
   end subroutine

Now, should an implementation want to support some form of handling the exception without the ERROR STOP with its inadvertent side effects, they might provide some wiring as follows:

   subroutine sub_e( x, *, e )
      real, intent(inout) :: x
      class(except_t), intent(out), optional :: e
      ! An arbitrary range that causes an exception
      if ( (x > 0.4).and.(x < 0.6) ) then
         e%procname = "sub"
         e%line = 17
         e%estat = 1
         e%emsg = "Invalid value of x"
         return 1
      end if
      x = x + 1.0
      return
   end subroutine

where an ALTERNATE RETURN is combined with an additional argument of OPTIONAL ATTRIBUTE which is of an arbitrary EXCEPTION class (say except_t) that can hold 'data' pertaining to the exception like so:

module except_m
   implicit none
   type :: except_t
      integer :: line = 0
      integer :: estat = 0
      character(len=:), allocatable :: procname
      character(len=:), allocatable :: emsg
   end type
end module

Implementation might also overload the library method so it can be invoked with or without such 'handling':

   generic :: sub => sub_e, sub_x

Then on the caller side the implementation might 'parse' out their own TRY-CATCH syntax with Fortran semantics described using the standard facility of BLOCK construct as follows:

try_catch: block
   use except_m, only : except_t
   type(except_t) :: e
   call sub(x, *1, e)  !<-- note here
   exit try_catch
   ! Catch exception section
1  continue
   print *, "Exception occured in ", e%procname, " at line # ", e%line
   print *, "Error status = ", e%estat
   print *, e%emsg
   print *, "Execution is continued"
end block try_catch

Thus when a caller decides for whatever reason not to use exception handling, the call sub(x) option remains.

A complete working example is

module except_m
   implicit none
   type :: except_t
      integer :: line = 0
      integer :: estat = 0
      character(len=:), allocatable :: procname
      character(len=:), allocatable :: emsg
   end type
end module
module m
   use except_m, only : except_t
   implicit none
   interface sub
      module procedure sub_e
      module procedure sub_x
   end interface
   !generic :: sub => sub_e, sub_x  !<-- Fortran 2018, not supported by gfortran
contains
   subroutine sub_e( x, *, e )
      real, intent(inout) :: x
      class(except_t), intent(out) :: e
      if ( (x > 0.4).and.(x < 0.6) ) then
         e%procname = "sub"
         e%line = 17
         e%estat = 1
         e%emsg = "Invalid value of x"
         return 1
      end if
      x = x + 1.0
      return
   end subroutine
   subroutine sub_x( x )
      real, intent(inout) :: x
      ! An arbitrary range that causes an exception
      if ( (x > 0.4).and.(x < 0.6) ) then
         error stop "In sub_x: invalid value of x"
      end if
      x = x + 1.0
      return
   end subroutine
end module
program p
   use m, only : sub
   implicit none
   real :: x
   integer ::  i

   blk1: block

      print *, "Block 1"
      do i = 1, 10
         call random_number( x )
         print *, "x = ", x

         try_catch: block
            use except_m, only : except_t
            type(except_t) :: e
            call sub(x, *1, e)
            exit try_catch
            ! Catch exception section
         1  continue
            print *, "Exception occured in ", e%procname, " at line # ", e%line
            print *, "Error status = ", e%estat
            print *, e%emsg
            print *, "Execution is continued"
         end block try_catch

      end do

      print *

   end block blk1

   blk2: block

      print *, "Block 2"

      do i = 1, 10

         call random_number( x )
         print *, "x = ", x

         call sub( x )

      end do

      print *

   end block blk2

   stop

end program p

Upon execution, output can be as follows:

 Block 1
 x =   0.414278448
 Exception occured in sub at line #           17
 Error status =            1
 Invalid value of x
 Execution is continued
 x =   0.313940883
 x =    3.59865427E-02
 x =   0.298014879
 x =    6.91809058E-02
 x =   0.301655293
 x =   0.212819457
 x =   0.951416492
 x =    7.51928091E-02
 x =   0.804682851

 Block 2
 x =   0.217340648
 x =   0.727119923
 x =   0.489649057
ERROR STOP In sub_x: invalid value of x

Error termination. Backtrace:

Could not print backtrace: libbacktrace could not find executable to open
#0  0xffffffff
#1  0xffffffff
#2  0xffffffff
#3  0xffffffff
#4  0xffffffff
#5  0xffffffff
#6  0xffffffff
#7  0xffffffff
#8  0xffffffff
#9  0xffffffff
everythingfunctional commented 4 years ago

@FortranFan , that looks intriguing. How far could you get with that technique by just utilizing a good error handling framework/library like this one? You could make the errors an optional argument and put a stop statement if one isn't provided.

FortranFan commented 4 years ago

@everythingfunctional wrote:

@FortranFan , that looks intriguing. How far could you get with that technique by just utilizing a good error handling framework/library like this one? You could make the errors an optional argument and put a stop statement if one isn't provided.

@everythingfunctional, the basic idea of the example above in https://github.com/j3-fortran/fortran_proposals/issues/66#issuecomment-549082528 is that with limited syntax/keywords/intrinsic procedures a la how coarrays came about with [] syntax starting Fortran 2008, the processor can implement some form of exception handling by doing "all the wiring" by reusing what it has had to do to support the obsolescent but not deleted "alternate return" facility. What's shown in the example is just an attempt to show a simple-minded way a processor might go about doing this. That example is not meant as a library implementation.

everythingfunctional commented 4 years ago

@FortranFan , that example makes it look more like the code is trying to support a language feature than a language feature actually supporting more clear and concise code. I would much rather utilize a library than have the language require that much boiler plate/rigmarole.

In order to properly support exception handling you really need to add some keywords, constructs and intrinsics to the language.

try
    call somethingThatMightThrow()
catch(e)
type is (IOException)
    call doSomethingToRecover()
class is (Exception)
    call justCrash(e)
end try
throws subroutine somethingThatMightThrow()
   ...
   throw exception("Something went wrong")
   ...
end subroutine

Procedures that throw could (or should) not be able to be pure or elemental, and procedures that call procedures could (or must) inherit that they throw as well.

I think anything you tried to shoehorn in based on or around other features, or that wasn't designed well would probably not be a good idea. If Fortran does add in exception handling it should probably be based on good designs from other languages.

victorsndvg commented 4 years ago

Hi all,

I was playing with a dummy project called ForEx (https://github.com/victorsndvg/ForEx) to explore how to implement user defined exception handling in OO Fortran taking advantage of the preprocessor.

The experience was nice exploring the flexibility of the language to do this sort of things, but in my opinion I found a key point that unable Fortran to manage exceptions. Non-local jumps. As fas as I know, non-local jumps are not supported by the standard.

When handling exceptions, one wants to abruptly stops the program at a given point and manage the program behaviour in a different point. Without using non-local jumps one can only "handle exceptions vertically" and per block-of-code instead of per-instruction.

My proposal here is to support non-local jumps.

Hope to be helpful!